diff --git a/tunnel.go b/tunnel.go new file mode 100644 index 0000000..d2fb90a --- /dev/null +++ b/tunnel.go @@ -0,0 +1,195 @@ +package main + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "flag" + "io" + "log" + "math/big" + "net" + "net/http" + "os" + "time" + + "fmt" +) + +func exists(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func generateCert(host string) (string, string) { + var err error + + dir := "/tmp/https-certs/" + host + "/" + certPath := dir + "cert.pem" + keyPath := dir + "key.pem" + + if exists(certPath) && exists(keyPath) { + return certPath, keyPath + } + + log.Println("Generating new certificates...") + + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + log.Fatalf("failed to generate private key: %s", err) + } + + notBefore := time.Now() + notAfter := notBefore.Add(365 * 24 * time.Hour) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + IsCA: true, + + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + if ip := net.ParseIP(host); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, host) + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + err = os.MkdirAll(dir, 0700) + if err != nil { + log.Fatalf("Failed to write certificates: %s", err) + } + + certOut, err := os.Create(certPath) + if err != nil { + log.Fatalf("failed to open cert.pem for writing: %s", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + + keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Fatalf("failed to open key.pem for writing: %s", err) + } + + pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) + keyOut.Close() + return certPath, keyPath +} + +func handleTunneling(w http.ResponseWriter, r *http.Request) { + //conf:=&tls.Config{InsecureSkipVerify:true} + tcpAddr, err := net.ResolveTCPAddr("tcp", r.Host) + if err != nil { + println("ResolveTCPAddr failed:", err.Error()) + return + } + //investigate why net.Dial keeps failing while DialTCP works + dest_conn, err := net.DialTCP("tcp", nil, tcpAddr) + if err != nil { + fmt.Println("Connection failed to dest server") + http.Error(w, "Cant connect to dest", http.StatusServiceUnavailable) + return + } + //respond the server + w.WriteHeader(http.StatusOK) + + //Investigate why cant create client with http.Transport with proxy + //transport := http.Transport{ + // Proxy: http.ProxyURL(proxyURL), + // TLSClientConfig: &tls.Config{}, + //} + //Just use http.Hijacker + hijacker, ok := w.(http.Hijacker) + if !ok { + http.Error(w, "Hijacking not supported", http.StatusInternalServerError) + return + } + client_conn, _, err := hijacker.Hijack() + if err != nil { + fmt.Println("Client ERROR") + http.Error(w, err.Error(), http.StatusServiceUnavailable) + } + //Let the proxy begin + go transfer(dest_conn, client_conn) + go transfer(client_conn, dest_conn) +} +func transfer(destination io.WriteCloser, source io.ReadCloser) { + defer destination.Close() + defer source.Close() + //Copy the data + io.Copy(destination, source) + +} + +//Regular HTTP PRoxy +func handleHTTP(w http.ResponseWriter, req *http.Request) { + resp, err := http.DefaultTransport.RoundTrip(req) + if err != nil { + http.Error(w, err.Error(), http.StatusServiceUnavailable) + return + } + defer resp.Body.Close() + copyHeader(w.Header(), resp.Header) + w.WriteHeader(resp.StatusCode) + io.Copy(w, resp.Body) +} +func copyHeader(dst, src http.Header) { + for k, vv := range src { + for _, v := range vv { + dst.Add(k, v) + } + } +} +func main() { + + var proto string + flag.StringVar(&proto, "proto", "http", "Proxy protocol (http or https)") + flag.Parse() + if proto != "http" && proto != "https" { + log.Fatal("Protocol must be either http or https") + } + host := "165.227.74.36" + cert, key := generateCert(host) + + server := &http.Server{ + Addr: ":9000", + + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodConnect { + handleTunneling(w, r) + } else { + handleHTTP(w, r) + } + }), + // Disable HTTP/2. + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), + } + if proto == "http" { + log.Fatal(server.ListenAndServe()) + } else { + log.Fatal(server.ListenAndServeTLS(cert, key)) + } +}