Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
321 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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{}{} | ||
} |
Oops, something went wrong.