Compare commits
No commits in common. '7b15c8ff18322b2af016dbb66bf8a52c1212c6f6' and 'af79be21830e4b6d4c4210e18d7bc0d7451c4fe6' have entirely different histories.
7b15c8ff18
...
af79be2183
@ -1,5 +1,2 @@ |
|||||||
os=$1 |
|
||||||
arch=$2 |
|
||||||
|
|
||||||
export GOPATH=`pwd` |
export GOPATH=`pwd` |
||||||
GOOS=$os GOARCH=$arch go build -o bin/gomitmproxy$os$arch src/main/*.go |
go build -o bin/gomitmproxy src/main/*.go |
@ -1,2 +0,0 @@ |
|||||||
openssl genrsa -out gomitmproxy-ca-pk.pem 2048 |
|
||||||
openssl req -new -x509 -days 36500 -key gomitmproxy-ca-pk.pem -out gomitmproxy-ca-cert.pem |
|
@ -1,19 +1,19 @@ |
|||||||
-----BEGIN CERTIFICATE----- |
-----BEGIN CERTIFICATE----- |
||||||
MIIDFjCCAf4CCQCGwNBaM3AUxTANBgkqhkiG9w0BAQsFADBMMQswCQYDVQQGEwJj |
MIIDIjCCAgqgAwIBAgIIFFyG4pw+r+kwDQYJKoZIhvcNAQELBQAwLzEXMBUGA1UE |
||||||
bjEUMBIGA1UECgwLZ29taXRtcHJveHkxDjAMBgNVBAsMBXByb3h5MRcwFQYDVQQD |
ChMOZ29taXRtcHJveHkxLjAxFDASBgNVBAMTC2dvbWl0bXByb3h5MB4XDTE2MDUy |
||||||
DA5nb21pdG1wcm94eTEuMjAgFw0xODAzMjkwNzMxNTJaGA8yMTE4MDMwNTA3MzE1 |
OTEwMjQ0NloXDTE3MDYyOTEwMjQ0NlowLzEXMBUGA1UEChMOZ29taXRtcHJveHkx |
||||||
MlowTDELMAkGA1UEBhMCY24xFDASBgNVBAoMC2dvbWl0bXByb3h5MQ4wDAYDVQQL |
LjAxFDASBgNVBAMTC2dvbWl0bXByb3h5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A |
||||||
DAVwcm94eTEXMBUGA1UEAwwOZ29taXRtcHJveHkxLjIwggEiMA0GCSqGSIb3DQEB |
MIIBCgKCAQEA5i5e0Ioniij/zx4uP+pEwIfCPuqRHpmtxnylXtDJNS7Mibsl57cC |
||||||
AQUAA4IBDwAwggEKAoIBAQDamLQMN7B77J5hQNZTXQ7IohPJQx6nbq3PteQ+2+Ia |
pKDymhGtehXKzt/7lv7Zh4PQyHUAPJQqTp6Q/bQRcnCEKoY0jSCDy7ZYxz+6YMth |
||||||
d4Mllczxe8855BpLW1kTsgPTk/1tZcw05//megxDncLyUUIiQh3MUc25XOTuNe3Z |
JD9CG67jCio78hQDyYQc8RpUJ8FyH+HWE8xyPkixnGC9tZSG6bAtP2ORtn9AsKXX |
||||||
RgYUNemt+n7MVxoq9DLbk4nWnpBJEYHBokaAZfOejo4rzdjZEWQCZRFocPuFTvOw |
8Hj9zWKrQqWFMbQvOp8SGVkqNsO/ZvIO3Rf5pBPlPTUTsjXMFFN2PipoMgZnF43C |
||||||
6slPUZY1urp4fzvlQY5qOcatk+DkuB+FI4REsuSTu0kbRyVHoS5xiVRJPI6X0fZB |
eXt3de+PxFfs+nN2IIPbLTmRHW8qrworkJ0TBJWYMiT6mFOEiqlF92pzdLNDyR3e |
||||||
dRLqhJujrNqzYnyARMAzcRIV04FyDNY+2hxD3ViRL+XcMa/ksymloApqprAzvgxR |
xednS3We07jZBAbvQGmy1eenZORXZsDuRQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC |
||||||
w5v8ob7GwTlxyJS4lxY8GptYPvxcXHF8bbyUCTLD+/OfAgMBAAEwDQYJKoZIhvcN |
AqQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMB |
||||||
AQELBQADggEBAE3PSKbLIJTSX0E98nsguKspAzfExBwUbQSe8RIxDjOFmIiqqgPa |
Af8wDQYJKoZIhvcNAQELBQADggEBAH1Gj0AtwzwbM0+NPrGsv2B0z/BLLDHu9MIN |
||||||
wW4P1LEdQtKWMIUcCubNj7RnpOo/2t1+rqSSnRUUQ8WpzBQ85WnpaR2pXdgkl1OA |
EhVIPgcGj9E9e3uP2uzxBBfFIeIk9w2GyuzqHqq/NTOJSf3CpL0RJYI9gS8qMg4x |
||||||
LVYMgJU1cUHYD++ljVLnSDq7rOPe5TmVeycYejfkdXVP+WFsM5g8QEFYoQz6a16u |
wn5wkSPVb8cp36jS3rNvpNx6nuz+P+H+t1x4+KWk12GM2gz7g/YSmkXOtntQzpOW |
||||||
e75rM3JQu8sGNox5iktmcmnGkaYXr6XcbNEy1m0b5jXbmI2k4ffciE2Qs66nV/kT |
Gl9ydNYWbd0ad5k+kQhBp2nsq1NByg3fwEd1jgvSnxKB+P76OK01pSxRczLpCp0F |
||||||
3tg7fTN7Ptlu0pbqbpFgjd/cyFNKyq7yDbIyI0Y5Vpz44ZrkMQpjzN6wAiR5va2q |
+1gpD6ukiPIC1RZCUiHk4UeY8jmpBNgNW4OJmxgA+LFclB3hytkxN2qbnk5xfA33 |
||||||
jg839LQ9Qcd7Qd01Jgyg2tyvoBFqzgx0puM= |
1JciXh8y0ZD1wXB7y14PAB1TB53Rgjvk38OrPpBf3nETxlqP5zQ= |
||||||
-----END CERTIFICATE----- |
-----END CERTIFICATE----- |
||||||
|
@ -1,27 +1,27 @@ |
|||||||
-----BEGIN RSA PRIVATE KEY----- |
-----BEGIN RSA PRIVATE KEY----- |
||||||
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 |
||||||
-----END RSA PRIVATE KEY----- |
-----END RSA PRIVATE KEY----- |
||||||
|
@ -1,84 +0,0 @@ |
|||||||
package color |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"runtime" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
TextBlack = iota + 30 |
|
||||||
TextRed |
|
||||||
TextGreen |
|
||||||
TextYellow |
|
||||||
TextBlue |
|
||||||
TextMagenta |
|
||||||
TextCyan |
|
||||||
TextWhite |
|
||||||
) |
|
||||||
|
|
||||||
func Black(str string) string { |
|
||||||
return textColor(TextBlack, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Red(str string) string { |
|
||||||
return textColor(TextRed, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Green(str string) string { |
|
||||||
return textColor(TextGreen, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Yellow(str string) string { |
|
||||||
return textColor(TextYellow, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Blue(str string) string { |
|
||||||
return textColor(TextBlue, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Magenta(str string) string { |
|
||||||
return textColor(TextMagenta, str) |
|
||||||
} |
|
||||||
|
|
||||||
func Cyan(str string) string { |
|
||||||
return textColor(TextCyan, str) |
|
||||||
} |
|
||||||
|
|
||||||
func White(str string) string { |
|
||||||
return textColor(TextWhite, str) |
|
||||||
} |
|
||||||
|
|
||||||
func textColor(color int, str string) string { |
|
||||||
if IsWindows() { |
|
||||||
return str |
|
||||||
} |
|
||||||
|
|
||||||
switch color { |
|
||||||
case TextBlack: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextBlack, str) |
|
||||||
case TextRed: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextRed, str) |
|
||||||
case TextGreen: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextGreen, str) |
|
||||||
case TextYellow: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextYellow, str) |
|
||||||
case TextBlue: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextBlue, str) |
|
||||||
case TextMagenta: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextMagenta, str) |
|
||||||
case TextCyan: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextCyan, str) |
|
||||||
case TextWhite: |
|
||||||
return fmt.Sprintf("\x1b[0;%dm%s\x1b[0m", TextWhite, str) |
|
||||||
default: |
|
||||||
return str |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func IsWindows() bool { |
|
||||||
if runtime.GOOS == "windows" { |
|
||||||
return true |
|
||||||
} else { |
|
||||||
return false |
|
||||||
} |
|
||||||
} |
|
@ -1,56 +0,0 @@ |
|||||||
package config |
|
||||||
|
|
||||||
import "crypto/tls" |
|
||||||
|
|
||||||
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_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_RSA_WITH_AES_128_CBC_SHA256, |
|
||||||
tls.TLS_RSA_WITH_AES_128_GCM_SHA256, |
|
||||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, |
|
||||||
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_ECDSA_WITH_AES_128_CBC_SHA256, |
|
||||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, |
|
||||||
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, |
|
||||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, |
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, |
|
||||||
tls.TLS_FALLBACK_SCSV, |
|
||||||
}, |
|
||||||
PreferServerCipherSuites: true, |
|
||||||
}, |
|
||||||
} |
|
||||||
} |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@ -1,74 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"config" |
|
||||||
"flag" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"mitm" |
|
||||||
"mylog" |
|
||||||
"net/http" |
|
||||||
"os" |
|
||||||
"sync" |
|
||||||
) |
|
||||||
|
|
||||||
//func main() {
|
|
||||||
//
|
|
||||||
// println("Hello, Running Success!")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func main() { |
|
||||||
var log io.WriteCloser |
|
||||||
var err error |
|
||||||
// cofig
|
|
||||||
conf := new(conf.Cfg) |
|
||||||
conf.Port = flag.String("port", "8080", "端口") |
|
||||||
conf.Raddr = flag.String("raddr", "", "远程地址") |
|
||||||
conf.Log = flag.String("logFile", "", "本地日志路径") |
|
||||||
conf.Monitor = flag.Bool("m", false, "监听模式(捕获内容)") |
|
||||||
conf.Tls = flag.Bool("tls", false, "是否启用tls连接") |
|
||||||
//pool := flag.String("pool", "", "代理池地址,格式 ip:端口 ")
|
|
||||||
|
|
||||||
flag.Parse() |
|
||||||
|
|
||||||
// init log
|
|
||||||
if *conf.Log != "" { |
|
||||||
log, err = os.Create(*conf.Log) |
|
||||||
if err != nil { |
|
||||||
mylog.Fatalln("创建日志文件失败 " + err.Error()) |
|
||||||
} |
|
||||||
} else { |
|
||||||
log = os.Stderr |
|
||||||
} |
|
||||||
mylog.SetLog(log) |
|
||||||
|
|
||||||
// add current node to remote pool
|
|
||||||
//go regToPool(*pool,*conf.Port,*conf.Tls)
|
|
||||||
|
|
||||||
// init tls config
|
|
||||||
tlsConfig := config.NewTlsConfig("gomitmproxy-ca-pk.pem", "gomitmproxy-ca-cert.pem", "", "") |
|
||||||
// start mitm proxy
|
|
||||||
wg := new(sync.WaitGroup) |
|
||||||
wg.Add(1) |
|
||||||
mitm.Gomitmproxy(conf, tlsConfig, wg) |
|
||||||
wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
// 注册自己到代理池中心
|
|
||||||
func regToPool(pool string, port string, proto bool) { |
|
||||||
mylog.Println("开始注册到代理池 ") |
|
||||||
protoStr := "http" |
|
||||||
if proto { |
|
||||||
protoStr = "https" |
|
||||||
} |
|
||||||
pollURL := "http://" + pool + "/v2/add?port=" + port + "&proto=" + protoStr |
|
||||||
mylog.Println("url: %s ", pollURL) |
|
||||||
resp, err := http.Get(pollURL) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("出现错误 ", err.Error()) |
|
||||||
} |
|
||||||
defer resp.Body.Close() |
|
||||||
bodyBytes, err := ioutil.ReadAll(resp.Body) |
|
||||||
mylog.Println("代理池返回结果:" + string(bodyBytes)) |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,41 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"config" |
||||||
|
"flag" |
||||||
|
"io" |
||||||
|
"mitm" |
||||||
|
"mylog" |
||||||
|
"os" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
var log io.WriteCloser |
||||||
|
var err error |
||||||
|
// cofig
|
||||||
|
conf := new(config.Cfg) |
||||||
|
conf.Port = flag.String("port", "8080", "Listen port") |
||||||
|
conf.Raddr = flag.String("raddr", "", "Remote addr") |
||||||
|
conf.Log = flag.String("logFile", "", "log file path") |
||||||
|
conf.Monitor = flag.Bool("m", false, "monitor mode") |
||||||
|
conf.Tls = flag.Bool("tls", false, "tls connect") |
||||||
|
flag.Parse() |
||||||
|
|
||||||
|
// init log
|
||||||
|
if *conf.Log != "" { |
||||||
|
log, err = os.Create(*conf.Log) |
||||||
|
if err != nil { |
||||||
|
mylog.Fatalln("fail to create log file!") |
||||||
|
} |
||||||
|
} else { |
||||||
|
log = os.Stderr |
||||||
|
} |
||||||
|
mylog.SetLog(log) |
||||||
|
|
||||||
|
// start mitm proxy
|
||||||
|
wg := new(sync.WaitGroup) |
||||||
|
wg.Add(1) |
||||||
|
mitm.Gomitmproxy(conf, wg) |
||||||
|
wg.Wait() |
||||||
|
} |
@ -1,74 +0,0 @@ |
|||||||
package main |
|
||||||
|
|
||||||
import ( |
|
||||||
"config" |
|
||||||
"flag" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"mitm" |
|
||||||
"mylog" |
|
||||||
"net/http" |
|
||||||
"os" |
|
||||||
"sync" |
|
||||||
) |
|
||||||
|
|
||||||
//func main() {
|
|
||||||
//
|
|
||||||
// println("Hello, Running Success!")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func main() { |
|
||||||
var log io.WriteCloser |
|
||||||
var err error |
|
||||||
// cofig
|
|
||||||
conf := new(conf.Cfg) |
|
||||||
conf.Port = flag.String("port", "8080", "端口") |
|
||||||
conf.Raddr = flag.String("raddr", "", "远程地址") |
|
||||||
conf.Log = flag.String("logFile", "", "本地日志路径") |
|
||||||
conf.Monitor = flag.Bool("m", false, "监听模式(捕获内容)") |
|
||||||
conf.Tls = flag.Bool("tls", false, "是否启用tls连接") |
|
||||||
//pool := flag.String("pool", "", "代理池地址,格式 ip:端口 ")
|
|
||||||
|
|
||||||
flag.Parse() |
|
||||||
|
|
||||||
// init log
|
|
||||||
if *conf.Log != "" { |
|
||||||
log, err = os.Create(*conf.Log) |
|
||||||
if err != nil { |
|
||||||
mylog.Fatalln("创建日志文件失败 " + err.Error()) |
|
||||||
} |
|
||||||
} else { |
|
||||||
log = os.Stderr |
|
||||||
} |
|
||||||
mylog.SetLog(log) |
|
||||||
|
|
||||||
// add current node to remote pool
|
|
||||||
//go regToPool(*pool,*conf.Port,*conf.Tls)
|
|
||||||
|
|
||||||
// init tls config
|
|
||||||
tlsConfig := config.NewTlsConfig("gomitmproxy-ca-pk.pem", "gomitmproxy-ca-cert.pem", "", "") |
|
||||||
// start mitm proxy
|
|
||||||
wg := new(sync.WaitGroup) |
|
||||||
wg.Add(1) |
|
||||||
mitm.Gomitmproxy(conf, tlsConfig, wg) |
|
||||||
wg.Wait() |
|
||||||
} |
|
||||||
|
|
||||||
// 注册自己到代理池中心
|
|
||||||
func regToPool(pool string, port string, proto bool) { |
|
||||||
mylog.Println("开始注册到代理池 ") |
|
||||||
protoStr := "http" |
|
||||||
if proto { |
|
||||||
protoStr = "https" |
|
||||||
} |
|
||||||
pollURL := "http://" + pool + "/v2/add?port=" + port + "&proto=" + protoStr |
|
||||||
mylog.Println("url: %s ", pollURL) |
|
||||||
resp, err := http.Get(pollURL) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("出现错误 ", err.Error()) |
|
||||||
} |
|
||||||
defer resp.Body.Close() |
|
||||||
bodyBytes, err := ioutil.ReadAll(resp.Body) |
|
||||||
mylog.Println("代理池返回结果:" + string(bodyBytes)) |
|
||||||
|
|
||||||
} |
|
@ -1,48 +0,0 @@ |
|||||||
// package cache implements a really primitive cache that associates expiring
|
|
||||||
// values with string keys. This cache never clears itself out.
|
|
||||||
package mitm |
|
||||||
|
|
||||||
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)} |
|
||||||
} |
|
@ -1,100 +0,0 @@ |
|||||||
package mitm |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"bytes" |
|
||||||
"color" |
|
||||||
"compress/flate" |
|
||||||
"compress/gzip" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"math" |
|
||||||
"mylog" |
|
||||||
"net/http" |
|
||||||
"strconv" |
|
||||||
) |
|
||||||
|
|
||||||
func httpDump(reqDump []byte, 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 = color.Green("<--" + strconv.Itoa(respStatus)) |
|
||||||
case 3: |
|
||||||
respStatusStr = color.Yellow("<--" + strconv.Itoa(respStatus)) |
|
||||||
case 4: |
|
||||||
respStatusStr = color.Magenta("<--" + strconv.Itoa(respStatus)) |
|
||||||
case 5: |
|
||||||
respStatusStr = color.Red("<--" + strconv.Itoa(respStatus)) |
|
||||||
} |
|
||||||
|
|
||||||
fmt.Println(color.Green("Request:"), respStatusStr) |
|
||||||
req, _ := ParseReq(reqDump) |
|
||||||
fmt.Printf("%s %s %s\n", color.Blue(req.Method), req.Host+req.RequestURI, respStatusStr) |
|
||||||
fmt.Printf("%s %s\n", color.Blue("RemoteAddr:"), req.RemoteAddr) |
|
||||||
for headerName, headerContext := range req.Header { |
|
||||||
fmt.Printf("%s: %s\n", color.Blue(headerName), headerContext) |
|
||||||
} |
|
||||||
|
|
||||||
if req.Method == "POST" { |
|
||||||
fmt.Println(color.Green("POST Param:")) |
|
||||||
err := req.ParseForm() |
|
||||||
if err != nil { |
|
||||||
mylog.Println("parseForm error:", err) |
|
||||||
} else { |
|
||||||
for k, v := range req.Form { |
|
||||||
fmt.Printf("\t%s: %s\n", color.Blue(k), v) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
fmt.Println(color.Green("Response:")) |
|
||||||
for headerName, headerContext := range resp.Header { |
|
||||||
fmt.Printf("%s: %s\n", color.Blue(headerName), headerContext) |
|
||||||
} |
|
||||||
|
|
||||||
respBody, err := ioutil.ReadAll(resp.Body) |
|
||||||
if err != nil { |
|
||||||
mylog.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 { |
|
||||||
mylog.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", color.Black("####################"), color.Cyan("END"), color.Black("####################")) |
|
||||||
} |
|
||||||
|
|
||||||
func ParseReq(b []byte) (*http.Request, error) { |
|
||||||
// func ReadRequest(b *bufio.Reader) (req *Request, err error) { return readRequest(b, deleteHostHeader) }
|
|
||||||
fmt.Println(string(b)) |
|
||||||
fmt.Println("-----------------------") |
|
||||||
var buf io.ReadWriter |
|
||||||
buf = new(bytes.Buffer) |
|
||||||
buf.Write(b) |
|
||||||
bufr := bufio.NewReader(buf) |
|
||||||
return http.ReadRequest(bufr) |
|
||||||
} |
|
@ -1,310 +0,0 @@ |
|||||||
// Package keyman provides convenience APIs around Go's built-in crypto APIs.
|
|
||||||
package mitm |
|
||||||
|
|
||||||
import ( |
|
||||||
"crypto/rand" |
|
||||||
"crypto/rsa" |
|
||||||
"crypto/x509" |
|
||||||
"crypto/x509/pkix" |
|
||||||
"encoding/pem" |
|
||||||
"fmt" |
|
||||||
"io/ioutil" |
|
||||||
"math/big" |
|
||||||
"mylog" |
|
||||||
"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 { |
|
||||||
mylog.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 { |
|
||||||
mylog.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 { |
|
||||||
mylog.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} |
|
||||||
} |
|
@ -1,45 +0,0 @@ |
|||||||
// This example shows a proxy server that uses go-mitm to man-in-the-middle
|
|
||||||
// HTTPS connections opened with CONNECT requests
|
|
||||||
|
|
||||||
package mitm |
|
||||||
|
|
||||||
import ( |
|
||||||
"config" |
|
||||||
"mylog" |
|
||||||
"net/http" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func Gomitmproxy(conf *config.Cfg, tlsConfig *config.TlsConfig, wg *sync.WaitGroup) { |
|
||||||
handler, err := InitConfig(conf, tlsConfig) |
|
||||||
if err != nil { |
|
||||||
mylog.Fatalf("InitConfig error: %s", err) |
|
||||||
} |
|
||||||
|
|
||||||
server := &http.Server{ |
|
||||||
Addr: ":" + *conf.Port, |
|
||||||
Handler: handler, |
|
||||||
ReadTimeout: 1 * time.Hour, |
|
||||||
WriteTimeout: 1 * time.Hour, |
|
||||||
} |
|
||||||
|
|
||||||
go func() { |
|
||||||
mylog.Printf("Gomitmproxy Listening On: %s", *conf.Port) |
|
||||||
if *conf.Tls { |
|
||||||
mylog.Println("Listen And Serve HTTP TLS") |
|
||||||
err = server.ListenAndServeTLS("gomitmproxy-ca-cert.pem", "gomitmproxy-ca-pk.pem") |
|
||||||
} else { |
|
||||||
mylog.Println("Listen And Serve HTTP") |
|
||||||
err = server.ListenAndServe() |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
mylog.Fatalf("Unable To Start HTTP proxy: %s", err) |
|
||||||
} |
|
||||||
|
|
||||||
wg.Done() |
|
||||||
mylog.Printf("Gomitmproxy Stop!!!!") |
|
||||||
}() |
|
||||||
|
|
||||||
return |
|
||||||
} |
|
@ -1,28 +0,0 @@ |
|||||||
package mitm |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
@ -1,393 +0,0 @@ |
|||||||
package mitm |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"config" |
|
||||||
"crypto/tls" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"log" |
|
||||||
"mylog" |
|
||||||
"net" |
|
||||||
"net/http" |
|
||||||
"net/http/httputil" |
|
||||||
"net/url" |
|
||||||
"regexp" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
Version = "1.1" |
|
||||||
ONE_DAY = 24 * time.Hour |
|
||||||
TWO_WEEKS = ONE_DAY * 14 |
|
||||||
ONE_MONTH = 1 |
|
||||||
ONE_YEAR = 1 |
|
||||||
) |
|
||||||
|
|
||||||
type HandlerWrapper struct { |
|
||||||
MyConfig *config.Cfg |
|
||||||
tlsConfig *config.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) { |
|
||||||
mylog.Println("DumpHTTPAndHTTPs") |
|
||||||
req.Header.Del("Proxy-Connection") |
|
||||||
req.Header.Set("Connection", "Keep-Alive") |
|
||||||
var reqDump []byte |
|
||||||
var err error |
|
||||||
ch := make(chan bool) |
|
||||||
// handle connection
|
|
||||||
go func() { |
|
||||||
reqDump, err = httputil.DumpRequestOut(req, true) |
|
||||||
ch <- true |
|
||||||
}() |
|
||||||
if err != nil { |
|
||||||
mylog.Println("DumpRequest error ", err) |
|
||||||
} |
|
||||||
connIn, _, err := resp.(http.Hijacker).Hijack() |
|
||||||
if err != nil { |
|
||||||
mylog.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 { |
|
||||||
mylog.Println("dial to", host, "error:", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
if err = req.Write(connOut); err != nil { |
|
||||||
mylog.Println("send to server error", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) |
|
||||||
if err != nil && err != io.EOF { |
|
||||||
mylog.Println("read response error:", err) |
|
||||||
} |
|
||||||
|
|
||||||
} else { |
|
||||||
if !matched { |
|
||||||
host += ":443" |
|
||||||
} |
|
||||||
|
|
||||||
connOut, err := tls.Dial("tcp", host, hw.tlsConfig.ServerTLSConfig) |
|
||||||
if err != nil { |
|
||||||
|
|
||||||
} |
|
||||||
if err = req.Write(connOut); err != nil { |
|
||||||
mylog.Println("tls dial to", host, "error:", err) |
|
||||||
return |
|
||||||
} |
|
||||||
if err = req.Write(connOut); err != nil { |
|
||||||
mylog.Println("send to server error", err) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) |
|
||||||
if err != nil && err != io.EOF { |
|
||||||
mylog.Println("read response error:", err) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
if respOut == nil { |
|
||||||
log.Println("respOut is nil") |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
respDump, err := httputil.DumpResponse(respOut, true) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("respDump error:", err) |
|
||||||
} |
|
||||||
|
|
||||||
_, err = connIn.Write(respDump) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("connIn write error:", err) |
|
||||||
} |
|
||||||
|
|
||||||
if *hw.MyConfig.Monitor { |
|
||||||
<-ch |
|
||||||
go httpDump(reqDump, respOut) |
|
||||||
} else { |
|
||||||
<-ch |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (hw *HandlerWrapper) ServeHTTP(resp http.ResponseWriter, req *http.Request) { |
|
||||||
raddr := *hw.MyConfig.Raddr |
|
||||||
if len(raddr) != 0 { |
|
||||||
hw.Forward(resp, req, raddr) |
|
||||||
} else { |
|
||||||
if req.Method == "CONNECT" { |
|
||||||
hw.https = true |
|
||||||
hw.InterceptHTTPs(resp, req) |
|
||||||
} else { |
|
||||||
hw.https = false |
|
||||||
hw.DumpHTTPAndHTTPs(resp, req) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func (hw *HandlerWrapper) InterceptHTTPs(resp http.ResponseWriter, req *http.Request) { |
|
||||||
mylog.Println("InterceptHTTPs") |
|
||||||
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 { |
|
||||||
mylog.Printf("Error serving mitm'ed connection: %s", err) |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
connIn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) |
|
||||||
} |
|
||||||
|
|
||||||
func (hw *HandlerWrapper) Forward(resp http.ResponseWriter, req *http.Request, raddr string) { |
|
||||||
connIn, _, err := resp.(http.Hijacker).Hijack() |
|
||||||
connOut, err := net.Dial("tcp", raddr) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("dial tcp error", err) |
|
||||||
} |
|
||||||
|
|
||||||
err = connectProxyServer(connOut, raddr) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("connectProxyServer error:", err) |
|
||||||
} |
|
||||||
|
|
||||||
if req.Method == "CONNECT" { |
|
||||||
b := []byte("HTTP/1.1 200 Connection Established\r\n" + |
|
||||||
"Proxy-Agent: gomitmproxy/" + Version + "\r\n" + |
|
||||||
"Content-Length: 0" + "\r\n\r\n") |
|
||||||
_, err := connIn.Write(b) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("Write Connect err:", err) |
|
||||||
return |
|
||||||
} |
|
||||||
} else { |
|
||||||
req.Header.Del("Proxy-Connection") |
|
||||||
req.Header.Set("Connection", "Keep-Alive") |
|
||||||
if err = req.Write(connOut); err != nil { |
|
||||||
mylog.Println("send to server err", err) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
err = Transport(connIn, connOut) |
|
||||||
if err != nil { |
|
||||||
mylog.Println("trans error ", err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func InitConfig(conf *config.Cfg, tlsConfig *config.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 copyHTTPRequest(template *http.Request) *http.Request { |
|
||||||
req := &http.Request{} |
|
||||||
if template != nil { |
|
||||||
*req = *template |
|
||||||
} |
|
||||||
return req |
|
||||||
} |
|
||||||
|
|
||||||
func respBadGateway(resp http.ResponseWriter, msg string) { |
|
||||||
log.Println(msg) |
|
||||||
resp.WriteHeader(502) |
|
||||||
resp.Write([]byte(msg)) |
|
||||||
} |
|
||||||
|
|
||||||
//两个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: |
|
||||||
} |
|
||||||
|
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
func MyCopy(src io.Reader, dst io.Writer, ch chan<- error) { |
|
||||||
_, err := io.Copy(dst, src) |
|
||||||
ch <- err |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
/*func ReadNotDrain(r *http.Request) (content []byte, err error) { |
|
||||||
content, err = ioutil.ReadAll(r.Body) |
|
||||||
r.Body = io.ReadCloser(bytes.NewBuffer(content)) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
func ParsePostValues(req *http.Request) (url.Values, error) { |
|
||||||
c, err := ReadNotDrain(req) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
values, err := url.ParseQuery(string(c)) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return values, nil |
|
||||||
} |
|
||||||
*/ |
|
@ -1,34 +0,0 @@ |
|||||||
package mylog |
|
||||||
|
|
||||||
import "log" |
|
||||||
import "io" |
|
||||||
|
|
||||||
var logger *log.Logger |
|
||||||
|
|
||||||
func init() { |
|
||||||
log.SetFlags(log.LstdFlags | log.Lshortfile) |
|
||||||
} |
|
||||||
|
|
||||||
func SetLog(l io.WriteCloser) { |
|
||||||
logger = log.New(l, "[gomitmproxy]", log.LstdFlags) |
|
||||||
} |
|
||||||
|
|
||||||
func Fatalf(format string, v ...interface{}) { |
|
||||||
logger.Fatalf(format, v) |
|
||||||
} |
|
||||||
|
|
||||||
func Fatalln(v ...interface{}) { |
|
||||||
logger.Fatalln(v) |
|
||||||
} |
|
||||||
|
|
||||||
func Printf(format string, v ...interface{}) { |
|
||||||
logger.Printf(format, v) |
|
||||||
} |
|
||||||
|
|
||||||
func Println(v ...interface{}) { |
|
||||||
logger.Println(v) |
|
||||||
} |
|
||||||
|
|
||||||
func Panicln(v ...interface{}) { |
|
||||||
logger.Panicln(v) |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
package mylog |
|
||||||
|
|
||||||
import ( |
|
||||||
"log" |
|
||||||
"os" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
func TestMyLog(t *testing.T) { |
|
||||||
logFile, err := os.Create("test.log") |
|
||||||
if err != nil { |
|
||||||
log.Fatalln("fail to create log file!") |
|
||||||
} |
|
||||||
logger := log.New(logFile, "[gomitmproxy]", log.LstdFlags|log.Llongfile) |
|
||||||
SetLog(logger) |
|
||||||
Println("log test") |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
[gomitmproxy]2017/04/03 00:00:25 /Users/bao/program/go/gowork/gomitmproxy/src/vendor/mylog/my_log.go:20: [log test] |
|
Loading…
Reference in new issue