Success path for fake tls is implemented

This commit is contained in:
9seconds
2019-11-07 13:04:38 +03:00
parent fd8506c82a
commit 038b2b200d
21 changed files with 777 additions and 111 deletions
+80
View File
@@ -0,0 +1,80 @@
package tlstypes
import (
"container/ring"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"strconv"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/config"
)
const (
connectionServerKeepCertificates = 5
connectionServerUpdateEvery = 10 * time.Minute
)
type connectionServer struct {
nextWriteItem *ring.Ring
nextReadItem *ring.Ring
ctx context.Context
channelGet chan chan<- *x509.Certificate
}
func (c *connectionServer) fetch() (*x509.Certificate, error) {
addr := net.JoinHostPort(config.C.CloakHost, strconv.Itoa(config.C.CloakPort))
conn, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true}) // nolint: gosec
if err != nil {
return nil, fmt.Errorf("cannot connect to the masked host: %w", err)
}
defer conn.Close()
if err = conn.Handshake(); err != nil {
return nil, fmt.Errorf("cannot perform tls handshake: %w", err)
}
certificates := conn.ConnectionState().PeerCertificates
if len(certificates) == 0 {
return nil, errors.New("no certificates is found")
}
return certificates[0], nil
}
func (c *connectionServer) run() {
logger := zap.S().Named("tls-connection-server")
ticker := time.NewTicker(connectionServerUpdateEvery)
defer ticker.Stop()
for {
select {
case <-c.ctx.Done():
return
case resp := <-c.channelGet:
resp <- c.nextReadItem.Value.(*x509.Certificate)
close(resp)
c.nextReadItem = c.nextReadItem.Next()
case <-ticker.C:
cert, err := c.fetch()
switch err {
case nil:
c.nextWriteItem.Value = cert
c.nextWriteItem = c.nextWriteItem.Next()
default:
logger.Warnw("cannot fetch certificates", "error", err)
}
}
}
}
+86
View File
@@ -0,0 +1,86 @@
package tlstypes
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"fmt"
"github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/utils"
)
type ClientHello struct {
Handshake
}
func (c ClientHello) Digest() []byte {
dirtyDigest := c.Random
c.Random = [32]byte{}
rec := Record{
Type: RecordTypeHandshake,
Version: Version10,
Data: &c,
}
mac := hmac.New(sha256.New, config.C.Secret)
mac.Write(rec.Bytes()) // nolint: errcheck
computedDigest := mac.Sum(nil)
for i := range computedDigest {
computedDigest[i] ^= dirtyDigest[i]
}
return computedDigest
}
func ParseClientHello(raw []byte) (*ClientHello, error) {
rv := &ClientHello{}
rv.Type = HandshakeType(raw[0])
if rv.Type != HandshakeTypeClient {
return nil, fmt.Errorf("incorrect handshake type %v", rv.Type)
}
raw = raw[1:]
sizeUint24 := utils.Uint24{}
copy(sizeUint24[:], utils.ReverseBytes(raw[:3]))
size := int(utils.FromUint24(sizeUint24))
raw = raw[3:]
if len(raw) != size {
return nil, fmt.Errorf("payload size mismatch (%d != %d)", len(raw), size)
}
versionRaw := raw[:2]
switch {
case bytes.Equal(versionRaw, Version13Bytes):
rv.Version = Version13
case bytes.Equal(versionRaw, Version12Bytes):
rv.Version = Version12
case bytes.Equal(versionRaw, Version11Bytes):
rv.Version = Version11
case bytes.Equal(versionRaw, Version10Bytes):
rv.Version = Version10
default:
return nil, fmt.Errorf("unknown protocol version %v", versionRaw)
}
raw = raw[2:]
copy(rv.Random[:], raw[:32])
raw = raw[32:]
sessionIDLength := int(raw[0])
raw = raw[1:]
rv.SessionID = make([]byte, sessionIDLength)
copy(rv.SessionID, raw)
raw = raw[sessionIDLength:]
tail := make([]byte, len(raw))
copy(tail, raw)
rv.Tail = RawBytes(tail)
return rv, nil
}
+79
View File
@@ -0,0 +1,79 @@
package tlstypes
type RecordType uint8
const (
RecordTypeHandshake RecordType = 0x16
RecordTypeApplicationData RecordType = 0x17
RecordTypeChangeCipherSpec RecordType = 0x14
)
type HandshakeType uint8
const (
HandshakeTypeClient HandshakeType = 0x01
HandshakeTypeServer HandshakeType = 0x02
)
type CipherSuiteType uint8
const (
CipherSuiteType_TLS_AES_128_GCM_SHA256 CipherSuiteType = iota // nolint: stylecheck, golint
CipherSuiteType_TLS_AES_256_GCM_SHA384 // nolint: stylecheck, golint
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256 // nolint: stylecheck, golint
)
func (c CipherSuiteType) Bytes() []byte {
switch c {
case CipherSuiteType_TLS_AES_128_GCM_SHA256:
return CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes
case CipherSuiteType_TLS_AES_256_GCM_SHA384:
return CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes
}
return CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes
}
type Version uint8
func (v Version) Bytes() []byte {
switch v {
case Version13:
return Version13Bytes
case Version12:
return Version12Bytes
case Version11:
return Version11Bytes
}
return Version10Bytes
}
const (
VersionUnknown Version = iota
Version10
Version11
Version12
Version13
)
var (
Version10Bytes = []byte{0x03, 0x01}
Version11Bytes = []byte{0x03, 0x02}
Version12Bytes = []byte{0x03, 0x03}
Version13Bytes = []byte{0x03, 0x04}
CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes = []byte{0x13, 0x01} // nolint: stylecheck, golint
CipherSuiteType_TLS_AES_256_GCM_SHA384_Bytes = []byte{0x13, 0x02} // nolint: stylecheck, golint
CipherSuiteType_TLS_CHACHA20_POLY1305_SHA256_Bytes = []byte{0x13, 0x03} // nolint; stylecheck, golint
)
type Byter interface {
Bytes() []byte
}
type RawBytes []byte
func (r RawBytes) Bytes() []byte {
return []byte(r)
}
+37
View File
@@ -0,0 +1,37 @@
package tlstypes
import (
"bytes"
"github.com/9seconds/mtg/utils"
)
type Handshake struct {
Type HandshakeType
Version Version
Random [32]byte
SessionID []byte
Tail Byter
}
func (h *Handshake) Bytes() []byte {
buf := bytes.Buffer{}
packetBuf := bytes.Buffer{}
buf.WriteByte(byte(h.Type))
packetBuf.Write(h.Version.Bytes())
packetBuf.Write(h.Random[:])
packetBuf.WriteByte(byte(len(h.SessionID)))
packetBuf.Write(h.SessionID)
packetBuf.Write(h.Tail.Bytes())
sizeUint24 := utils.ToUint24(uint32(packetBuf.Len()))
sizeUint24Bytes := sizeUint24[:]
sizeUint24Bytes[0], sizeUint24Bytes[2] = sizeUint24Bytes[2], sizeUint24Bytes[0]
buf.Write(sizeUint24Bytes)
packetBuf.WriteTo(&buf) // nolint: errcheck
return buf.Bytes()
}
+85
View File
@@ -0,0 +1,85 @@
package tlstypes
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
const recordMaxChunkSize = 16384 + 24
type Record struct {
Type RecordType
Version Version
Data Byter
}
func (r Record) Bytes() []byte {
buf := bytes.Buffer{}
data := r.Data.Bytes()
buf.WriteByte(byte(r.Type))
buf.Write(r.Version.Bytes())
binary.Write(&buf, binary.BigEndian, uint16(len(data))) // nolint: errcheck
buf.Write(data)
return buf.Bytes()
}
func ReadRecord(reader io.Reader) (Record, error) {
buf := [2]byte{}
rec := Record{}
if _, err := io.ReadFull(reader, buf[:1]); err != nil {
return rec, fmt.Errorf("cannot read record type: %w", err)
}
rec.Type = RecordType(buf[0])
if _, err := io.ReadFull(reader, buf[:]); err != nil {
return rec, fmt.Errorf("cannot read version: %w", err)
}
switch {
case bytes.Equal(buf[:], Version13Bytes):
rec.Version = Version13
case bytes.Equal(buf[:], Version12Bytes):
rec.Version = Version12
case bytes.Equal(buf[:], Version11Bytes):
rec.Version = Version11
case bytes.Equal(buf[:], Version10Bytes):
rec.Version = Version10
}
if _, err := io.ReadFull(reader, buf[:]); err != nil {
return rec, fmt.Errorf("cannot read data length: %w", err)
}
data := make([]byte, binary.BigEndian.Uint16(buf[:]))
if _, err := io.ReadFull(reader, data); err != nil {
return rec, fmt.Errorf("cannot read data: %w", err)
}
rec.Data = RawBytes(data)
return rec, nil
}
func MakeRecords(raw []byte) (arr []Record) {
for len(raw) > 0 {
chunkSize := recordMaxChunkSize
if chunkSize > len(raw) {
chunkSize = len(raw)
}
arr = append(arr, Record{
Type: RecordTypeApplicationData,
Version: Version12,
Data: RawBytes(raw[:chunkSize]),
})
raw = raw[chunkSize:]
}
return
}
+92
View File
@@ -0,0 +1,92 @@
package tlstypes
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
"golang.org/x/crypto/curve25519"
"github.com/9seconds/mtg/config"
)
type ServerHello struct {
Handshake
clientHello *ClientHello
}
func (s ServerHello) WelcomePacket(hostCert []byte) []byte {
s.Random = [32]byte{}
rec := Record{
Type: RecordTypeHandshake,
Version: Version12,
Data: &s,
}
buf := bytes.NewBuffer(rec.Bytes())
recChangeCipher := Record{
Type: RecordTypeChangeCipherSpec,
Version: Version12,
Data: RawBytes([]byte{0x01}),
}
buf.Write(recChangeCipher.Bytes())
recData := Record{
Type: RecordTypeApplicationData,
Version: Version12,
Data: RawBytes(hostCert),
}
buf.Write(recData.Bytes())
packet := buf.Bytes()
mac := hmac.New(sha256.New, config.C.Secret)
mac.Write(s.clientHello.Random[:]) // nolint: errcheck
mac.Write(packet) // nolint: errcheck
copy(packet[11:], mac.Sum(nil))
return packet
}
func NewServerHello(clientHello *ClientHello) *ServerHello {
rv := &ServerHello{
clientHello: clientHello,
}
rv.Type = HandshakeTypeServer
rv.Version = Version12
rv.SessionID = make([]byte, len(clientHello.SessionID))
copy(rv.SessionID, clientHello.SessionID)
tail := bytes.NewBuffer(CipherSuiteType_TLS_AES_128_GCM_SHA256_Bytes)
tail.WriteByte(0x00) // no compression
makeTLSExtensions(tail)
rv.Tail = RawBytes(tail.Bytes())
return rv
}
func makeTLSExtensions(buf io.Writer) {
buf.Write([]byte{ // nolint: errcheck
0x00, 0x2e, // 46 bytes of data
0x00, 0x33, // Extension - Key Share
0x00, 0x24, // 36 bytes
0x00, 0x1d, // x25519 curve
0x00, 0x20, // 32 bytes of key
})
var dst, in, base [32]byte
rand.Read(in[:]) // nolint: errcheck
rand.Read(base[:]) // nolint: errcheck
curve25519.ScalarMult(&dst, &in, &base)
buf.Write(dst[:]) // nolint: errcheck
buf.Write([]byte{ // nolint: errcheck
0x00, 0x2b, // Extension - Supported Versions
0x00, 0x02, // 2 bytes are following
0x03, 0x04, // TLS 1.3
})
}