Serve diagnostics and CA certificate

Fixes #21.  Reference:

https://mtersch.wordpress.com/2015/03/17/certificate-import-in-firefox-on-android/
This commit is contained in:
Andrew Gaul 2017-08-20 21:55:20 -07:00
parent 703cb4adaf
commit f69a0b7e0e
4 changed files with 98 additions and 10 deletions

View file

@ -31,7 +31,7 @@ var (
func main() { func main() {
flag.Parse() flag.Parse()
p := proxy.New() p := proxy.New(*host, cert)
if (*ca == "") != (*caKey == "") { if (*ca == "") != (*caKey == "") {
log.Fatalln("must specify both CA certificate and key") log.Fatalln("must specify both CA certificate and key")

View file

@ -37,7 +37,7 @@ var _ = Suite(&CompyTest{})
func (s *CompyTest) SetUpSuite(c *C) { func (s *CompyTest) SetUpSuite(c *C) {
s.server = httptest.NewServer(httpbin.GetMux()) s.server = httptest.NewServer(httpbin.GetMux())
s.proxy = proxy.New() s.proxy = proxy.New("localhost"+*host, nil)
s.proxy.AddTranscoder("image/gif", &tc.Gif{}) s.proxy.AddTranscoder("image/gif", &tc.Gif{})
s.proxy.AddTranscoder("image/jpeg", tc.NewJpeg(50)) s.proxy.AddTranscoder("image/jpeg", tc.NewJpeg(50))
s.proxy.AddTranscoder("image/png", &tc.Png{}) s.proxy.AddTranscoder("image/png", &tc.Png{})
@ -243,3 +243,20 @@ func (s *CompyTest) TestAuthentication(c *C) {
defer resp.Body.Close() defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 200) c.Assert(resp.StatusCode, Equals, 200)
} }
func (s *CompyTest) TestAdmin(c *C) {
resp, err := s.client.Get("http://localhost" + *host)
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 200)
resp, err = s.client.Get("http://localhost" + *host + "/cacert")
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 404)
resp, err = s.client.Get("http://localhost" + *host + "/fake")
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 501)
}

View file

@ -6,14 +6,16 @@ import (
) )
type mitmListener struct { type mitmListener struct {
c chan net.Conn c chan net.Conn
cf *certFaker cf *certFaker
config *tls.Config
} }
func newMitmListener(cf *certFaker) *mitmListener { func newMitmListener(cf *certFaker, config *tls.Config) *mitmListener {
return &mitmListener{ return &mitmListener{
c: make(chan net.Conn), c: make(chan net.Conn),
cf: cf, cf: cf,
config: config,
} }
} }
@ -30,7 +32,7 @@ func (l *mitmListener) Addr() net.Addr {
} }
func (l *mitmListener) Serve(conn net.Conn, host string) (net.Conn, error) { func (l *mitmListener) Serve(conn net.Conn, host string) (net.Conn, error) {
sconn, err := tls.Dial("tcp", host, nil) sconn, err := tls.Dial("tcp", host, l.config)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -1,11 +1,17 @@
package proxy package proxy
import ( import (
"crypto/tls"
"crypto/x509"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"strings" "strings"
"sync/atomic" "sync/atomic"
) )
@ -17,16 +23,20 @@ type Proxy struct {
WriteCount uint64 WriteCount uint64
user string user string
pass string pass string
host string
cert *string
} }
type Transcoder interface { type Transcoder interface {
Transcode(*ResponseWriter, *ResponseReader, http.Header) error Transcode(*ResponseWriter, *ResponseReader, http.Header) error
} }
func New() *Proxy { func New(host string, cert *string) *Proxy {
p := &Proxy{ p := &Proxy{
transcoders: make(map[string]Transcoder), transcoders: make(map[string]Transcoder),
ml: nil, ml: nil,
host: host,
cert: cert,
} }
return p return p
} }
@ -36,7 +46,25 @@ func (p *Proxy) EnableMitm(ca, key string) error {
if err != nil { if err != nil {
return err return err
} }
p.ml = newMitmListener(cf)
var config *tls.Config
if p.cert != nil {
roots, err := x509.SystemCertPool()
if err != nil {
return err
}
pem, err := ioutil.ReadFile(*p.cert)
if err != nil {
return err
}
ok := roots.AppendCertsFromPEM([]byte(pem))
if !ok {
return errors.New("failed to parse root certificate")
}
config = &tls.Config{RootCAs: roots}
}
p.ml = newMitmListener(cf, config)
go http.Serve(p.ml, p) go http.Serve(p.ml, p)
return nil return nil
} }
@ -95,6 +123,14 @@ func (p *Proxy) handle(w http.ResponseWriter, r *http.Request) error {
return p.handleConnect(w, r) return p.handleConnect(w, r)
} }
host := r.URL.Host
if host == "" {
host = r.Host
}
if hostname, err := os.Hostname(); host == p.host || (err == nil && host == hostname+p.host) {
return p.handleLocalRequest(w, r)
}
resp, err := forward(r) resp, err := forward(r)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -112,6 +148,39 @@ func (p *Proxy) handle(w http.ResponseWriter, r *http.Request) error {
return err return err
} }
func (p *Proxy) handleLocalRequest(w http.ResponseWriter, r *http.Request) error {
if r.Method == "GET" && (r.URL.Path == "" || r.URL.Path == "/") {
w.Header().Set("Content-Type", "text/html")
read := atomic.LoadUint64(&p.ReadCount)
written := atomic.LoadUint64(&p.WriteCount)
io.WriteString(w, fmt.Sprintf(`<html>
<head>
<title>compy</title>
</head>
<body>
<h1>compy</h1>
<ul>
<li>total transcoded: %d -> %d (%3.1f%%)</li>
<li><a href="/cacert">CA cert</a></li>
<li><a href="https://github.com/barnacs/compy">GitHub</a></li>
</ul>
</body>
</html>`, read, written, float64(written)/float64(read)*100))
return nil
} else if r.Method == "GET" && r.URL.Path == "/cacert" {
if p.cert == nil {
http.NotFound(w, r)
return nil
}
w.Header().Set("Content-Type", "application/x-x509-ca-cert")
http.ServeFile(w, r, *p.cert)
return nil
} else {
w.WriteHeader(http.StatusNotImplemented)
return nil
}
}
func forward(r *http.Request) (*http.Response, error) { func forward(r *http.Request) (*http.Response, error) {
if r.URL.Scheme == "" { if r.URL.Scheme == "" {
if r.TLS != nil && r.TLS.ServerName == r.Host { if r.TLS != nil && r.TLS.ServerName == r.Host {