mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2025-01-15 13:10:18 +01:00
290 lines
8 KiB
Go
290 lines
8 KiB
Go
|
// Copyright 2015 Google Inc. All rights reserved.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
// Package mitm provides tooling for MITMing TLS connections. It provides
|
||
|
// tooling to create CA certs and generate TLS configs that can be used to MITM
|
||
|
// a TLS connection with a provided CA certificate.
|
||
|
package mitm
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"crypto/rand"
|
||
|
"crypto/rsa"
|
||
|
"crypto/sha1"
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"crypto/x509/pkix"
|
||
|
"errors"
|
||
|
"math/big"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/google/martian/log"
|
||
|
)
|
||
|
|
||
|
// 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))
|
||
|
|
||
|
// Config is a set of configuration values that are used to build TLS configs
|
||
|
// capable of MITM.
|
||
|
type Config struct {
|
||
|
ca *x509.Certificate
|
||
|
capriv interface{}
|
||
|
priv *rsa.PrivateKey
|
||
|
keyID []byte
|
||
|
validity time.Duration
|
||
|
org string
|
||
|
getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
|
||
|
roots *x509.CertPool
|
||
|
skipVerify bool
|
||
|
handshakeErrorCallback func(*http.Request, error)
|
||
|
|
||
|
certmu sync.RWMutex
|
||
|
certs map[string]*tls.Certificate
|
||
|
}
|
||
|
|
||
|
// NewAuthority creates a new CA certificate and associated
|
||
|
// private key.
|
||
|
func NewAuthority(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(-validity),
|
||
|
NotAfter: time.Now().Add(validity),
|
||
|
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
|
||
|
}
|
||
|
|
||
|
// NewConfig creates a MITM config using the CA certificate and
|
||
|
// private key to generate on-the-fly certificates.
|
||
|
func NewConfig(ca *x509.Certificate, privateKey interface{}) (*Config, error) {
|
||
|
roots := x509.NewCertPool()
|
||
|
roots.AddCert(ca)
|
||
|
|
||
|
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)
|
||
|
pkixpub, err := x509.MarshalPKIXPublicKey(pub)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
h := sha1.New()
|
||
|
h.Write(pkixpub)
|
||
|
keyID := h.Sum(nil)
|
||
|
|
||
|
return &Config{
|
||
|
ca: ca,
|
||
|
capriv: privateKey,
|
||
|
priv: priv,
|
||
|
keyID: keyID,
|
||
|
validity: time.Hour,
|
||
|
org: "Martian Proxy",
|
||
|
certs: make(map[string]*tls.Certificate),
|
||
|
roots: roots,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
// SetValidity sets the validity window around the current time that the
|
||
|
// certificate is valid for.
|
||
|
func (c *Config) SetValidity(validity time.Duration) {
|
||
|
c.validity = validity
|
||
|
}
|
||
|
|
||
|
// SkipTLSVerify skips the TLS certification verification check.
|
||
|
func (c *Config) SkipTLSVerify(skip bool) {
|
||
|
c.skipVerify = skip
|
||
|
}
|
||
|
|
||
|
// SetOrganization sets the organization of the certificate.
|
||
|
func (c *Config) SetOrganization(org string) {
|
||
|
c.org = org
|
||
|
}
|
||
|
|
||
|
// SetHandshakeErrorCallback sets the handshakeErrorCallback function.
|
||
|
func (c *Config) SetHandshakeErrorCallback(cb func(*http.Request, error)) {
|
||
|
c.handshakeErrorCallback = cb
|
||
|
}
|
||
|
|
||
|
// HandshakeErrorCallback calls the handshakeErrorCallback function in this
|
||
|
// Config, if it is non-nil. Request is the connect request that this handshake
|
||
|
// is being executed through.
|
||
|
func (c *Config) HandshakeErrorCallback(r *http.Request, err error) {
|
||
|
if c.handshakeErrorCallback != nil {
|
||
|
c.handshakeErrorCallback(r, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TLS returns a *tls.Config that will generate certificates on-the-fly using
|
||
|
// the SNI extension in the TLS ClientHello.
|
||
|
func (c *Config) TLS() *tls.Config {
|
||
|
return &tls.Config{
|
||
|
InsecureSkipVerify: c.skipVerify,
|
||
|
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||
|
if clientHello.ServerName == "" {
|
||
|
return nil, errors.New("mitm: SNI not provided, failed to build certificate")
|
||
|
}
|
||
|
|
||
|
return c.cert(clientHello.ServerName)
|
||
|
},
|
||
|
NextProtos: []string{"http/1.1"},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TLSForHost returns a *tls.Config that will generate certificates on-the-fly
|
||
|
// using SNI from the connection, or fall back to the provided hostname.
|
||
|
func (c *Config) TLSForHost(hostname string) *tls.Config {
|
||
|
return &tls.Config{
|
||
|
InsecureSkipVerify: c.skipVerify,
|
||
|
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||
|
host := clientHello.ServerName
|
||
|
if host == "" {
|
||
|
host = hostname
|
||
|
}
|
||
|
|
||
|
return c.cert(host)
|
||
|
},
|
||
|
NextProtos: []string{"http/1.1"},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *Config) cert(hostname string) (*tls.Certificate, error) {
|
||
|
// Remove the port if it exists.
|
||
|
host, _, err := net.SplitHostPort(hostname)
|
||
|
if err == nil {
|
||
|
hostname = host
|
||
|
}
|
||
|
|
||
|
c.certmu.RLock()
|
||
|
tlsc, ok := c.certs[hostname]
|
||
|
c.certmu.RUnlock()
|
||
|
|
||
|
if ok {
|
||
|
log.Debugf("mitm: cache hit for %s", hostname)
|
||
|
|
||
|
// Check validity of the certificate for hostname match, expiry, etc. In
|
||
|
// particular, if the cached certificate has expired, create a new one.
|
||
|
if _, err := tlsc.Leaf.Verify(x509.VerifyOptions{
|
||
|
DNSName: hostname,
|
||
|
Roots: c.roots,
|
||
|
}); err == nil {
|
||
|
return tlsc, nil
|
||
|
}
|
||
|
|
||
|
log.Debugf("mitm: invalid certificate in cache for %s", hostname)
|
||
|
}
|
||
|
|
||
|
log.Debugf("mitm: cache miss for %s", hostname)
|
||
|
|
||
|
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{c.org},
|
||
|
},
|
||
|
SubjectKeyId: c.keyID,
|
||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||
|
BasicConstraintsValid: true,
|
||
|
NotBefore: time.Now().Add(-c.validity),
|
||
|
NotAfter: time.Now().Add(c.validity),
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
tlsc = &tls.Certificate{
|
||
|
Certificate: [][]byte{raw, c.ca.Raw},
|
||
|
PrivateKey: c.priv,
|
||
|
Leaf: x509c,
|
||
|
}
|
||
|
|
||
|
c.certmu.Lock()
|
||
|
c.certs[hostname] = tlsc
|
||
|
c.certmu.Unlock()
|
||
|
|
||
|
return tlsc, nil
|
||
|
}
|