parent
6a999726d2
commit
3d4555e1df
@ -1,81 +1,342 @@ |
|||||||
package main |
package main |
||||||
|
|
||||||
import ( |
import ( |
||||||
"flag" |
"bufio" |
||||||
|
"crypto/tls" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
"log" |
"log" |
||||||
|
"net" |
||||||
"net/http" |
"net/http" |
||||||
"os" |
"net/http/httputil" |
||||||
|
"net/url" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
"sync" |
"sync" |
||||||
"time" |
"time" |
||||||
) |
) |
||||||
|
|
||||||
const ( |
type HandlerWrapper struct { |
||||||
Version = "1.1" |
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 |
||||||
|
} |
||||||
|
|
||||||
var ( |
func (hw *HandlerWrapper) GenerateCertForClient() (err error) { |
||||||
wg sync.WaitGroup |
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 logFile *os.File |
var respOut *http.Response |
||||||
var logger *log.Logger |
host := req.Host |
||||||
|
|
||||||
func main() { |
matched, _ := regexp.MatchString(":[0-9]+$", host) |
||||||
var conf Cfg |
|
||||||
|
|
||||||
conf.Port = flag.String("port", "8080", "Listen port") |
if !hw.https { |
||||||
conf.Raddr = flag.String("raddr", "", "Remote addr") |
if !matched { |
||||||
conf.Log = flag.String("log", "./error.log", "log file path") |
host += ":80" |
||||||
conf.Monitor = flag.Bool("m", false, "monitor mode") |
} |
||||||
conf.Tls = flag.Bool("tls", false, "tls connect") |
|
||||||
flag.Parse() |
|
||||||
|
|
||||||
var err error |
connOut, err := net.DialTimeout("tcp", host, time.Second*30) |
||||||
logFile, err = os.Create(*conf.Log) |
|
||||||
if err != nil { |
if err != nil { |
||||||
log.Fatalln("fail to create log file!") |
logger.Println("dial to", host, "error:", err) |
||||||
|
return |
||||||
} |
} |
||||||
|
|
||||||
logger = log.New(logFile, "[gomitmproxy]", log.LstdFlags|log.Llongfile) |
if err = req.Write(connOut); err != nil { |
||||||
|
logger.Println("send to server error", err) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
wg.Add(1) |
respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) |
||||||
gomitmproxy(&conf) |
if err != nil && err != io.EOF { |
||||||
wg.Wait() |
logger.Println("read response error:", err) |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
if !matched { |
||||||
|
host += ":443" |
||||||
} |
} |
||||||
|
|
||||||
func gomitmproxy(conf *Cfg) { |
connOut, err := tls.Dial("tcp", host, hw.tlsConfig.ServerTLSConfig) |
||||||
tlsConfig := NewTlsConfig("gomitmproxy-ca-pk.pem", "gomitmproxy-ca-cert.pem", "", "") |
|
||||||
|
|
||||||
handler, err := InitConfig(conf, tlsConfig) |
|
||||||
if err != nil { |
if err != nil { |
||||||
logger.Fatalf("InitConfig error: %s", err) |
logger.Panicln("tls dial to", host, "error:", err) |
||||||
|
return |
||||||
|
} |
||||||
|
if err = req.Write(connOut); err != nil { |
||||||
|
logger.Println("send to server error", err) |
||||||
|
return |
||||||
} |
} |
||||||
|
|
||||||
server := &http.Server{ |
respOut, err = http.ReadResponse(bufio.NewReader(connOut), req) |
||||||
Addr: ":" + *conf.Port, |
if err != nil && err != io.EOF { |
||||||
Handler: handler, |
logger.Println("read response error:", err) |
||||||
ReadTimeout: 1 * time.Hour, |
|
||||||
WriteTimeout: 1 * time.Hour, |
|
||||||
} |
} |
||||||
|
|
||||||
go func() { |
} |
||||||
log.Printf("proxy listening port:%s", *conf.Port) |
|
||||||
|
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 *conf.Tls { |
if *hw.MyConfig.Monitor { |
||||||
log.Println("ListenAndServeTLS") |
go httpDump(req, respOut) |
||||||
err = server.ListenAndServeTLS("gomitmproxy-ca-cert.pem", "gomitmproxy-ca-pk.pem") |
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
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 { |
} else { |
||||||
log.Println("ListenAndServe") |
hw.https = false |
||||||
err = server.ListenAndServe() |
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 { |
if err != nil { |
||||||
logger.Fatalf("Unable to start HTTP proxy: %s", err) |
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) |
||||||
|
|
||||||
wg.Done() |
}) |
||||||
|
|
||||||
log.Printf("gomitmproxy stop!!!!") |
go func() { |
||||||
|
err = http.Serve(listener, handler) |
||||||
|
if err != nil && err != io.EOF { |
||||||
|
logger.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 { |
||||||
|
logger.Println("dial tcp error", err) |
||||||
|
} |
||||||
|
|
||||||
|
err = connectProxyServer(connOut, raddr) |
||||||
|
if err != nil { |
||||||
|
logger.Println("connectProxyServer error:", err) |
||||||
|
} |
||||||
|
|
||||||
|
if req.Method == "CONNECT" { |
||||||
|
b := []byte("HTTP/1.1 200 Connection Established\r\n" + |
||||||
|
"Proxy-Agent: gomitmproxy/" + Version + "\r\n\r\n") |
||||||
|
_, err := connIn.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(connOut); err != nil { |
||||||
|
logger.Println("send to server err", err) |
||||||
return |
return |
||||||
} |
} |
||||||
|
} |
||||||
|
err = Transport(connIn, connOut) |
||||||
|
if err != nil { |
||||||
|
log.Println("trans error ", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func InitConfig(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)) |
||||||
|
} |
||||||
|
|
||||||
|
//两个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 |
||||||
|
} |
||||||
|
Loading…
Reference in new issue