Skip to content

Commit

Permalink
Add MITM proxy for HTTPS requests
Browse files Browse the repository at this point in the history
  • Loading branch information
dstotijn committed Nov 23, 2019
1 parent c48562e commit ef4f829
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 37 deletions.
176 changes: 176 additions & 0 deletions cert.go
@@ -0,0 +1,176 @@
package main

import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"math/big"
"net"
"time"
)

// MaxSerialNumber is the upper boundary that is used to create unique serial
// numbers for the certificate. This can be any unsigned integer up to 20
// bytes (2^(8*20)-1).
var MaxSerialNumber = big.NewInt(0).SetBytes(bytes.Repeat([]byte{255}, 20))

// CertConfig is a set of configuration values that are used to build TLS configs
// capable of MITM
type CertConfig struct {
ca *x509.Certificate
caPriv crypto.PrivateKey
priv *rsa.PrivateKey
keyID []byte
}

// NewCertConfig creates a MITM config using the CA certificate and
// private key to generate on-the-fly certificates.
func NewCertConfig(ca *x509.Certificate, caPrivKey crypto.PrivateKey) (*CertConfig, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
pub := priv.Public()

// Subject Key Identifier support for end entity certificate.
// https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2)
pkixPubKey, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
h := sha1.New()
h.Write(pkixPubKey)
keyID := h.Sum(nil)

return &CertConfig{
ca: ca,
caPriv: caPrivKey,
priv: priv,
keyID: keyID,
}, nil
}

// NewCA creates a new CA certificate and associated private key.
func NewCA(name, organization string, validity time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
pub := priv.Public()

// Subject Key Identifier support for end entity certificate.
// https://www.ietf.org/rfc/rfc3280.txt (section 4.2.1.2)
pkixpub, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, nil, err
}
h := sha1.New()
h.Write(pkixpub)
keyID := h.Sum(nil)

// TODO: keep a map of used serial numbers to avoid potentially reusing a
// serial multiple times.
serial, err := rand.Int(rand.Reader, MaxSerialNumber)
if err != nil {
return nil, nil, err
}

tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: name,
Organization: []string{organization},
},
SubjectKeyId: keyID,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
NotBefore: time.Now().Add(-24 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
DNSNames: []string{name},
IsCA: true,
}

raw, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, pub, priv)
if err != nil {
return nil, nil, err
}

// Parse certificate bytes so that we have a leaf certificate.
x509c, err := x509.ParseCertificate(raw)
if err != nil {
return nil, nil, err
}

return x509c, priv, nil
}

// TLSConfig returns a *tls.Config that will generate certificates on-the-fly using
// the SNI extension in the TLS ClientHello.
func (c *CertConfig) TLSConfig() *tls.Config {
return &tls.Config{
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if clientHello.ServerName == "" {
return nil, errors.New("missing server name (SNI)")
}
return c.cert(clientHello.ServerName)
},
NextProtos: []string{"http/1.1"},
}
}

func (c *CertConfig) cert(hostname string) (*tls.Certificate, error) {
// Remove the port if it exists.
host, _, err := net.SplitHostPort(hostname)
if err == nil {
hostname = host
}

serial, err := rand.Int(rand.Reader, MaxSerialNumber)
if err != nil {
return nil, err
}

tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: hostname,
Organization: []string{"Gurp"},
},
SubjectKeyId: c.keyID,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
NotBefore: time.Now().Add(-24 * time.Hour),
NotAfter: time.Now().Add(24 * time.Hour),
}

if ip := net.ParseIP(hostname); ip != nil {
tmpl.IPAddresses = []net.IP{ip}
} else {
tmpl.DNSNames = []string{hostname}
}

raw, err := x509.CreateCertificate(rand.Reader, tmpl, c.ca, c.priv.Public(), c.caPriv)
if err != nil {
return nil, err
}

// Parse certificate bytes so that we have a leaf certificate.
x509c, err := x509.ParseCertificate(raw)
if err != nil {
return nil, err
}

return &tls.Certificate{
Certificate: [][]byte{raw, c.ca.Raw},
PrivateKey: c.priv,
Leaf: x509c,
}, nil
}
Empty file added go.sum
Empty file.
28 changes: 25 additions & 3 deletions main.go
Expand Up @@ -2,21 +2,43 @@ package main

import (
"crypto/tls"
"crypto/x509"
"flag"
"log"
"net/http"
)

var (
caCertFile = flag.String("cert", "", "CA certificate file path")
caKeyFile = flag.String("key", "", "CA private key file path")
)

func main() {
proxy := NewProxy()
flag.Parse()

tlsCA, err := tls.LoadX509KeyPair(*caCertFile, *caKeyFile)
if err != nil {
log.Fatalf("[FATAL] Could not load CA key pair: %v", err)
}

caCert, err := x509.ParseCertificate(tlsCA.Certificate[0])
if err != nil {
log.Fatalf("[FATAL] Could not parse CA: %v", err)
}

proxy, err := NewProxy(caCert, tlsCA.PrivateKey)
if err != nil {
log.Fatalf("[FATAL] Could not create Proxy: %v", err)
}

s := &http.Server{
Addr: ":8080",
Handler: proxy,
TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){}, // Disable HTTP/2
}

err := s.ListenAndServe()
err = s.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.Fatalf("HTTP server closed: %v", err)
log.Fatalf("[FATAL] HTTP server closed: %v", err)
}
}
5 changes: 4 additions & 1 deletion modd.conf
@@ -1,3 +1,6 @@
@cert = $HOME/.ssh/gurp_cert.pem
@key = $HOME/.ssh/gurp_key.pem

**/*.go {
daemon +sigterm: go run .
daemon +sigterm: go run . -cert @cert -key @key
}
47 changes: 47 additions & 0 deletions net.go
@@ -0,0 +1,47 @@
package main

import (
"errors"
"net"
)

var ErrAlreadyAccepted = errors.New("listener already accepted")

// OnceListener implements net.Listener.
//
// Accepts a connection once and returns an error on subsequent
// attempts.
type OnceAcceptListener struct {
c net.Conn
}

func (l *OnceAcceptListener) Accept() (net.Conn, error) {
if l.c == nil {
return nil, ErrAlreadyAccepted
}

c := l.c
l.c = nil

return c, nil
}

func (l *OnceAcceptListener) Close() error {
return nil
}

func (l *OnceAcceptListener) Addr() net.Addr {
return l.c.LocalAddr()
}

// ConnNotify embeds net.Conn and adds a channel field for notifying
// that the connection was closed.
type ConnNotify struct {
net.Conn
closed chan struct{}
}

func (c *ConnNotify) Close() {
c.Conn.Close()
c.closed <- struct{}{}
}

0 comments on commit ef4f829

Please sign in to comment.