Skip to content

Commit 7a6ed94

Browse files
Merge pull request #3 from go-httpproxy/develop
v1.2
2 parents ce8bc1f + ec3d375 commit 7a6ed94

File tree

7 files changed

+183
-61
lines changed

7 files changed

+183
-61
lines changed

LICENSE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2018, go-httpproxy authors.
1+
Copyright (c) 2018, "httpproxy" authors.
22
All rights reserved.
33

44
Redistribution and use in source and binary forms, with or without
@@ -8,7 +8,7 @@ modification, are permitted provided that the following conditions are met:
88
* Redistributions in binary form must reproduce the above copyright
99
notice, this list of conditions and the following disclaimer in the
1010
documentation and/or other materials provided with the distribution.
11-
* Neither the name of the go-httpproxy authors nor the
11+
* Neither the name of the "httpproxy" authors nor the
1212
names of its contributors may be used to endorse or promote products
1313
derived from this software without specific prior written permission.
1414

README.md

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
1-
# A Go HTTP proxy library which has KISS principle
1+
# Go HTTP proxy library
22

3-
## Introduction
3+
[![GoDoc](https://godoc.org/github.com/go-httpproxy/httpproxy?status.svg)](https://godoc.org/github.com/go-httpproxy/httpproxy)
44

5-
`github.com/go-httpproxy/httpproxy` repository provides an HTTP proxy library
6-
for Go (golang).
7-
8-
The library is regular HTTP proxy; supports HTTP, HTTPS through CONNECT. And
9-
also provides HTTPS connection using "Man in the Middle" style attack.
5+
Package httpproxy provides a customizable HTTP proxy; supports HTTP, HTTPS through
6+
CONNECT. And also provides HTTPS connection using "Man in the Middle" style
7+
attack.
108

119
It's easy to use. `httpproxy.Proxy` implements `Handler` interface of `net/http`
1210
package to offer `http.ListenAndServe` function.
1311

14-
### Keep it simple, stupid!
15-
16-
> KISS is an acronym for "Keep it simple, stupid" as a design principle. The
17-
KISS principle states that most systems work best if they are kept simple rather
18-
than made complicated; therefore simplicity should be a key goal in design and
19-
unnecessary complexity should be avoided. [Wikipedia]
20-
2112
## Usage
2213

2314
Library has two significant structs: Proxy and Context.
@@ -27,7 +18,7 @@ Library has two significant structs: Proxy and Context.
2718
```go
2819
// Proxy defines parameters for running an HTTP Proxy. It implements
2920
// http.Handler interface for ListenAndServe function. If you need, you must
30-
// fill Proxy struct before handling requests.
21+
// set Proxy struct before handling requests.
3122
type Proxy struct {
3223
// Session number of last proxy request.
3324
SessionNo int64
@@ -68,8 +59,12 @@ type Proxy struct {
6859
OnResponse func(ctx *Context, req *http.Request, resp *http.Response)
6960

7061
// If ConnectAction is ConnectMitm, it sets chunked to Transfer-Encoding.
71-
// By default, it is true.
62+
// By default, true.
7263
MitmChunked bool
64+
65+
// HTTP Authentication type. If it's not specified (""), uses "Basic".
66+
// By default, "".
67+
AuthType string
7368
}
7469
```
7570

@@ -176,11 +171,3 @@ func main() {
176171
http.ListenAndServe(":8080", prx)
177172
}
178173
```
179-
180-
## GoDoc
181-
182-
[https://godoc.org/github.com/go-httpproxy/httpproxy](https://godoc.org/github.com/go-httpproxy/httpproxy)
183-
184-
## To-Do
185-
186-
* GoDoc

ca.go

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import (
1010
mrand "math/rand"
1111
"net"
1212
"sort"
13+
"sync"
1314
"time"
1415
)
1516

16-
// Default certificate.
17+
// DefaultCaCert provides default CA certificate.
1718
var DefaultCaCert = []byte(`-----BEGIN CERTIFICATE-----
1819
MIIFkzCCA3ugAwIBAgIJAKEbW2ujNjX9MA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
1920
BAYTAlRSMREwDwYDVQQIDAhJc3RhbmJ1bDEVMBMGA1UECgwMZ28taHR0cHByb3h5
@@ -47,7 +48,7 @@ Ii9Vb07WDMQXou0ZZs7rnjAKo+sfFElTFewtS1wif4ZYBUJN1ln9G8qKaxbAiElm
4748
MgzNfZ7WlnaJf2rfHJbvK9VqJ9z6dLRYPjCHhakJBtzsMdxysEGJ
4849
-----END CERTIFICATE-----`)
4950

50-
// Default key.
51+
// DefaultCaKey provides default CA key.
5152
var DefaultCaKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
5253
MIIJKQIBAAKCAgEA18cwaaZzhdDEpUXpR9pkYRqsSdT30WhynFhFtcaBOf4eYdpt
5354
AJWL2ipo3Ac6bh+YgWfywG4prrSfWOJl+dQ59w439vLek/waBcEeFx+wJ6PFu0ur
@@ -100,7 +101,82 @@ s39uFDUnxsMb2Nl3JcNJHYBTm9ubjAZSo/3NuB0z/Gm+ssOcExTD//vW7BxxSAcs
100101
/xlPPTPbY5qoMAT7kK71kd4Ypnqbcs3UPpAHtcPkjWpuWOlebK0J7UYToj4f
101102
-----END RSA PRIVATE KEY-----`)
102103

103-
func signHosts(ca tls.Certificate, hosts []string) (cert tls.Certificate, error error) {
104+
// CaSigner is a certificate signer by CA certificate. It supports caching.
105+
type CaSigner struct {
106+
// Ca specifies CA certificate. You must set before using.
107+
Ca *tls.Certificate
108+
109+
mu sync.RWMutex
110+
certMap map[string]*tls.Certificate
111+
certList []string
112+
certIndex int
113+
certMax int
114+
}
115+
116+
// NewCaSigner returns a new CaSigner without caching.
117+
func NewCaSigner() *CaSigner {
118+
return NewCaSignerCache(0)
119+
}
120+
121+
// NewCaSignerCache returns a new CaSigner with caching given max.
122+
func NewCaSignerCache(max int) *CaSigner {
123+
if max < 0 {
124+
max = 0
125+
}
126+
return &CaSigner{
127+
certMap: make(map[string]*tls.Certificate),
128+
certList: make([]string, max),
129+
certIndex: 0,
130+
certMax: max,
131+
}
132+
}
133+
134+
// SignHost generates TLS certificate given single host, signed by CA certificate.
135+
func (c *CaSigner) SignHost(host string) (cert *tls.Certificate) {
136+
if host == "" {
137+
return
138+
}
139+
if c.certMax <= 0 {
140+
crt, err := SignHosts(*c.Ca, []string{host})
141+
if err != nil {
142+
return nil
143+
}
144+
cert = crt
145+
return
146+
}
147+
func() {
148+
c.mu.RLock()
149+
defer c.mu.RUnlock()
150+
cert = c.certMap[host]
151+
}()
152+
if cert != nil {
153+
return
154+
}
155+
c.mu.Lock()
156+
defer c.mu.Unlock()
157+
cert = c.certMap[host]
158+
if cert != nil {
159+
return
160+
}
161+
crt, err := SignHosts(*c.Ca, []string{host})
162+
if err != nil {
163+
return nil
164+
}
165+
cert = crt
166+
if len(c.certMap) >= c.certMax {
167+
delete(c.certMap, c.certList[c.certIndex])
168+
}
169+
c.certMap[host] = cert
170+
c.certList[c.certIndex] = host
171+
c.certIndex++
172+
if c.certIndex >= c.certMax {
173+
c.certIndex = 0
174+
}
175+
return
176+
}
177+
178+
// SignHosts generates TLS certificate given hosts, signed by CA certificate.
179+
func SignHosts(ca tls.Certificate, hosts []string) (cert *tls.Certificate, error error) {
104180
var x509ca *x509.Certificate
105181
if x509ca, error = x509.ParseCertificate(ca.Certificate[0]); error != nil {
106182
return
@@ -136,7 +212,7 @@ func signHosts(ca tls.Certificate, hosts []string) (cert tls.Certificate, error
136212
if derBytes, error = x509.CreateCertificate(rnd, &template, x509ca, &certPriv.PublicKey, ca.PrivateKey); error != nil {
137213
return
138214
}
139-
return tls.Certificate{
215+
return &tls.Certificate{
140216
Certificate: [][]byte{derBytes, ca.Certificate[0]},
141217
PrivateKey: certPriv,
142218
}, nil

connrespwr.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"sync"
99
)
1010

11+
// ConnResponseWriter implements http.ResponseWriter interface to use hijacked
12+
// HTTP connection.
1113
type ConnResponseWriter struct {
1214
Conn net.Conn
1315
mu sync.Mutex
@@ -16,14 +18,17 @@ type ConnResponseWriter struct {
1618
headersSent bool
1719
}
1820

21+
// NewConnResponseWriter returns a new ConnResponseWriter.
1922
func NewConnResponseWriter(conn net.Conn) *ConnResponseWriter {
2023
return &ConnResponseWriter{Conn: conn, header: make(http.Header)}
2124
}
2225

26+
// Header returns the header map that will be sent by WriteHeader.
2327
func (c *ConnResponseWriter) Header() http.Header {
2428
return c.header
2529
}
2630

31+
// Write writes the data to the connection as part of an HTTP reply.
2732
func (c *ConnResponseWriter) Write(body []byte) (int, error) {
2833
c.mu.Lock()
2934
defer c.mu.Unlock()
@@ -46,10 +51,12 @@ func (c *ConnResponseWriter) Write(body []byte) (int, error) {
4651
return c.Conn.Write(body)
4752
}
4853

54+
// WriteHeader sends an HTTP response header with status code.
4955
func (c *ConnResponseWriter) WriteHeader(code int) {
5056
c.code = code
5157
}
5258

59+
// Close closes network connection.
5360
func (c *ConnResponseWriter) Close() error {
5461
return c.Conn.Close()
5562
}

doing.go

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,42 @@ func doAuth(ctx *Context, w http.ResponseWriter, r *http.Request) bool {
3636
if ctx.Prx.OnAuth == nil {
3737
return false
3838
}
39-
authparts := strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
40-
if len(authparts) >= 2 {
41-
switch authparts[0] {
42-
case "Basic":
43-
userpassraw, err := base64.StdEncoding.DecodeString(authparts[1])
44-
if err == nil {
45-
userpass := strings.SplitN(string(userpassraw), ":", 2)
46-
if len(userpass) >= 2 && ctx.Prx.OnAuth(ctx, userpass[0], userpass[1]) {
47-
return false
39+
prxAuthType := ctx.Prx.AuthType
40+
if prxAuthType == "" {
41+
prxAuthType = "Basic"
42+
}
43+
unauthorized := false
44+
authParts := strings.SplitN(r.Header.Get("Proxy-Authorization"), " ", 2)
45+
if len(authParts) >= 2 {
46+
authType := authParts[0]
47+
authData := authParts[1]
48+
if prxAuthType == authType {
49+
unauthorized = true
50+
switch authType {
51+
case "Basic":
52+
userpassraw, err := base64.StdEncoding.DecodeString(authData)
53+
if err == nil {
54+
userpass := strings.SplitN(string(userpassraw), ":", 2)
55+
if len(userpass) >= 2 && ctx.Prx.OnAuth(ctx, userpass[0], userpass[1]) {
56+
return false
57+
}
4858
}
59+
default:
60+
unauthorized = false
4961
}
5062
}
5163
}
5264
if r.Close {
5365
defer r.Body.Close()
5466
}
55-
err := ServeInMemory(w, 407, map[string][]string{"Proxy-Authenticate": {"Basic"}},
56-
[]byte("Proxy Authentication Required"))
67+
respCode := 407
68+
respBody := "Proxy Authentication Required"
69+
if unauthorized {
70+
respCode = 401
71+
respBody = "Unauthorized"
72+
}
73+
err := ServeInMemory(w, respCode, map[string][]string{"Proxy-Authenticate": {prxAuthType}},
74+
[]byte(respBody))
5775
if err != nil && !isConnectionClosed(err) {
5876
doError(ctx, "Auth", ErrResponseWrite, err)
5977
}
@@ -88,9 +106,6 @@ func doConnect(ctx *Context, w http.ResponseWriter, r *http.Request) (w2 http.Re
88106
if ctx.Prx.OnConnect != nil {
89107
var newHost string
90108
ctx.ConnectAction, newHost = ctx.Prx.OnConnect(ctx, host)
91-
if ctx.ConnectAction == ConnectNone {
92-
ctx.ConnectAction = ConnectProxy
93-
}
94109
if newHost != "" {
95110
host = newHost
96111
}
@@ -138,13 +153,13 @@ func doConnect(ctx *Context, w http.ResponseWriter, r *http.Request) (w2 http.Re
138153
remoteConn.Close()
139154
case ConnectMitm:
140155
tlsConfig := &tls.Config{}
141-
cert, err := signHosts(ctx.Prx.Ca, []string{stripPort(host)})
142-
if err != nil {
156+
cert := ctx.Prx.signer.SignHost(stripPort(host))
157+
if cert == nil {
143158
hijConn.Close()
144159
doError(ctx, "Connect", ErrTLSSignHost, err)
145160
return
146161
}
147-
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
162+
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
148163
if _, err := hijConn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")); err != nil {
149164
hijConn.Close()
150165
if !isConnectionClosed(err) {
@@ -162,7 +177,8 @@ func doConnect(ctx *Context, w http.ResponseWriter, r *http.Request) (w2 http.Re
162177
}
163178
ctx.hijTLSReader = bufio.NewReader(ctx.hijTLSConn)
164179
w2 = NewConnResponseWriter(ctx.hijTLSConn)
165-
return
180+
default:
181+
hijConn.Close()
166182
}
167183
return
168184
}
@@ -219,7 +235,7 @@ func doRequest(ctx *Context, w http.ResponseWriter, r *http.Request) (bool, erro
219235
return true, err
220236
}
221237

222-
func doResponse(ctx *Context, w http.ResponseWriter, r *http.Request) (bool, error) {
238+
func doResponse(ctx *Context, w http.ResponseWriter, r *http.Request) error {
223239
resp, err := ctx.Prx.Rt.RoundTrip(r)
224240
if err != nil {
225241
if r.Close {
@@ -228,7 +244,11 @@ func doResponse(ctx *Context, w http.ResponseWriter, r *http.Request) (bool, err
228244
if err != context.Canceled && !isConnectionClosed(err) {
229245
doError(ctx, "Response", ErrRoundTrip, err)
230246
}
231-
return false, err
247+
err := ServeInMemory(w, 502, nil, []byte("Bad Gateway"))
248+
if err != nil && !isConnectionClosed(err) {
249+
doError(ctx, "Response", ErrResponseWrite, err)
250+
}
251+
return err
232252
}
233253
if ctx.Prx.OnResponse != nil {
234254
ctx.Prx.OnResponse(ctx, r, resp)
@@ -241,5 +261,5 @@ func doResponse(ctx *Context, w http.ResponseWriter, r *http.Request) (bool, err
241261
if err != nil && !isConnectionClosed(err) {
242262
doError(ctx, "Response", ErrResponseWrite, err)
243263
}
244-
return true, err
264+
return err
245265
}

0 commit comments

Comments
 (0)