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
+15 -5
View File
@@ -3,14 +3,24 @@ package antireplay
import "github.com/allegro/bigcache" import "github.com/allegro/bigcache"
type cache struct { type cache struct {
cache *bigcache.BigCache obfuscated2 *bigcache.BigCache
tls *bigcache.BigCache
} }
func (c *cache) Add(data []byte) { func (c *cache) AddObfuscated2(data []byte) {
c.cache.Set(string(data), nil) // nolint: errcheck c.obfuscated2.Set(string(data), nil) // nolint: errcheck
} }
func (c *cache) Has(data []byte) bool { func (c *cache) AddTLS(data []byte) {
_, err := c.cache.Get(string(data)) c.tls.Set(string(data), nil) // nolint: errcheck
}
func (c *cache) HasObfuscated2(data []byte) bool {
_, err := c.obfuscated2.Get(string(data))
return err == nil
}
func (c *cache) HasTLS(data []byte) bool {
_, err := c.tls.Get(string(data))
return err == nil return err == nil
} }
+13 -2
View File
@@ -14,7 +14,17 @@ var (
func Init() { func Init() {
initOnce.Do(func() { initOnce.Do(func() {
c, err := bigcache.NewBigCache(bigcache.Config{ c1, err := bigcache.NewBigCache(bigcache.Config{
Shards: 1024,
LifeWindow: config.C.AntiReplayEvictionTime,
Hasher: hasher{},
HardMaxCacheSize: config.C.AntiReplayMaxSize,
})
if err != nil {
panic(err)
}
c2, err := bigcache.NewBigCache(bigcache.Config{
Shards: 1024, Shards: 1024,
LifeWindow: config.C.AntiReplayEvictionTime, LifeWindow: config.C.AntiReplayEvictionTime,
Hasher: hasher{}, Hasher: hasher{},
@@ -25,7 +35,8 @@ func Init() {
} }
Cache = &cache{ Cache = &cache{
cache: c, obfuscated2: c1,
tls: c2,
} }
}) })
} }
+5 -6
View File
@@ -10,6 +10,7 @@ import (
"github.com/9seconds/mtg/antireplay" "github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/faketls"
"github.com/9seconds/mtg/hub" "github.com/9seconds/mtg/hub"
"github.com/9seconds/mtg/ntp" "github.com/9seconds/mtg/ntp"
"github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/obfuscated2"
@@ -75,6 +76,7 @@ func Proxy() error { // nolint: funlen
antireplay.Init() antireplay.Init()
telegram.Init() telegram.Init()
hub.Init(ctx) hub.Init(ctx)
faketls.Init(ctx)
proxyListener, err := net.Listen("tcp", config.C.Bind.String()) proxyListener, err := net.Listen("tcp", config.C.Bind.String())
if err != nil { if err != nil {
@@ -91,12 +93,9 @@ func Proxy() error { // nolint: funlen
Context: ctx, Context: ctx,
ClientProtocolMaker: obfuscated2.MakeClientProtocol, ClientProtocolMaker: obfuscated2.MakeClientProtocol,
} }
// if len(config.C.AdTag) == 0 { if config.C.SecretMode == config.SecretModeTLS {
// app.TelegramProtocolMaker = obfuscated2.MakeTelegramProtocol app.ClientProtocolMaker = faketls.MakeClientProtocol
// } }
// if config.C.SecretMode != config.SecretModeTLS {
// app.ClientProtocolMaker = obfuscated2.MakeClientProtocol
// }
app.Serve(proxyListener) app.Serve(proxyListener)
+3 -1
View File
@@ -28,11 +28,13 @@ func GetURLs() (urls IPURLs) {
secret = hex.EncodeToString(C.Secret) secret = hex.EncodeToString(C.Secret)
case SecretModeSecured: case SecretModeSecured:
secret = "dd" + hex.EncodeToString(C.Secret) secret = "dd" + hex.EncodeToString(C.Secret)
case SecretModeTLS:
secret = "ee" + hex.EncodeToString(C.Secret) + hex.EncodeToString([]byte(C.CloakHost))
} }
urls.IPv4 = makeURLs(C.PublicIPv4, secret) urls.IPv4 = makeURLs(C.PublicIPv4, secret)
urls.IPv6 = makeURLs(C.PublicIPv6, secret) urls.IPv6 = makeURLs(C.PublicIPv6, secret)
urls.BotSecret = secret urls.BotSecret = hex.EncodeToString(C.Secret)
return urls return urls
} }
+91
View File
@@ -0,0 +1,91 @@
package faketls
import (
"bytes"
"container/ring"
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"strconv"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/config"
)
type connectionServer struct {
nextWriteItem *ring.Ring
nextReadItem *ring.Ring
ctx context.Context
channelGet chan chan<- []byte
}
func (c *connectionServer) get() ([]byte, error) {
resp := make(chan []byte)
select {
case <-c.ctx.Done():
return nil, errors.New("context closed")
case c.channelGet <- resp:
return <-resp, nil
}
}
func (c *connectionServer) fetch() ([]byte, 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")
}
var buf bytes.Buffer
for _, v := range certificates {
buf.Write(v.Raw)
}
return buf.Bytes(), nil
}
func (c *connectionServer) run(tickEvery time.Duration) {
logger := zap.S().Named("tls-connection-server")
ticker := time.NewTicker(tickEvery)
defer ticker.Stop()
for {
select {
case <-c.ctx.Done():
return
case resp := <-c.channelGet:
resp <- c.nextReadItem.Value.([]byte)
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)
}
}
}
}
+71 -3
View File
@@ -2,9 +2,18 @@ package faketls
import ( import (
"bufio" "bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
"github.com/9seconds/mtg/antireplay"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/obfuscated2" "github.com/9seconds/mtg/obfuscated2"
"github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/tlstypes"
"github.com/9seconds/mtg/wrappers/stream" "github.com/9seconds/mtg/wrappers/stream"
) )
@@ -18,18 +27,77 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn
for _, expected := range faketlsStartBytes { for _, expected := range faketlsStartBytes {
if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected { if actual, err := bufferedReader.ReadByte(); err != nil || actual != expected {
return nil, c.simulateWebsite(rewinded) fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err)
return nil, errors.New("qqq")
} }
} }
rewinded.Rewind()
rewinded = stream.NewRewind(rewinded)
if err := c.tlsHandshake(rewinded); err != nil { if err := c.tlsHandshake(rewinded); err != nil {
return nil, c.simulateWebsite(rewinded) fmt.Println("!!!!!!!!!!!! ERROR !!!!!!!!!!!!", err)
return nil, errors.New("qqq")
} }
conn, err := c.ClientProtocol.Handshake(socket) conn := stream.NewFakeTLS(socket)
conn, err := c.ClientProtocol.Handshake(conn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return conn, err return conn, err
} }
func (c *ClientProtocol) tlsHandshake(conn io.ReadWriter) error {
helloRecord, err := tlstypes.ReadRecord(conn)
if err != nil {
return fmt.Errorf("cannot read initial record: %w", err)
}
clientHello, err := tlstypes.ParseClientHello(helloRecord.Data.Bytes())
if err != nil {
return fmt.Errorf("cannot parse client hello: %w", err)
}
digest := clientHello.Digest()
for i := 0; i < len(digest)-4; i++ {
if digest[i] != 0 {
return errBadDigest
}
}
timestamp := int64(binary.LittleEndian.Uint32(digest[len(digest)-4:]))
createdAt := time.Unix(timestamp, 0)
timeDiff := time.Since(createdAt)
if (timeDiff > TimeSkew || timeDiff < -TimeSkew) && timestamp > TimeFromBoot {
return errBadTime
}
if antireplay.Cache.HasTLS(clientHello.Random[:]) {
stats.Stats.AntiReplayDetected()
return errors.New("antireplay detected")
}
antireplay.Cache.AddTLS(clientHello.Random[:])
hostCert, err := connectionServerInstance.get()
if err != nil {
return fmt.Errorf("cannot get host certificate: %w", err)
}
serverHello := tlstypes.NewServerHello(clientHello)
serverHelloPacket := serverHello.WelcomePacket(hostCert)
if _, err := conn.Write(serverHelloPacket); err != nil {
return fmt.Errorf("cannot send welcome packet: %w", err)
}
return nil
}
func MakeClientProtocol() protocol.ClientProtocol {
return &ClientProtocol{}
}
+23 -14
View File
@@ -1,21 +1,30 @@
package faketls package faketls
import (
"errors"
"time"
)
const ( const (
TLSHandshakeLength = 1 + 2 + 2 + 512 TimeSkew = 5 * time.Second
TimeFromBoot = 24 * 60 * 60
) )
var ( var (
faketlsStartBytes = [...]byte{ errBadDigest = errors.New("bad digest")
0x16, errBadTime = errors.New("bad time")
0x03,
0x01, faketlsStartBytes = [...]byte{
0x02, 0x16,
0x00, 0x03,
0x01, 0x01,
0x00, 0x02,
0x01, 0x00,
0xfc, 0x01,
0x03, 0x00,
0x03, 0x01,
} 0xfc,
0x03,
0x03,
}
) )
+50
View File
@@ -0,0 +1,50 @@
package faketls
import (
"container/ring"
"context"
"sync"
"time"
"github.com/9seconds/mtg/config"
)
var (
connectionServerInstance connectionServer
connectionServerInitOnce sync.Once
)
const (
connectionServerKeepCertificates = 5
connectionServerUpdateEvery = 10 * time.Minute
)
func Init(ctx context.Context) {
connectionServerInitOnce.Do(func() {
if config.C.CloakHost == "" {
return
}
connectionServerInstance = connectionServer{
channelGet: make(chan chan<- []byte),
ctx: ctx,
}
cert, err := connectionServerInstance.fetch()
if err != nil {
panic(err)
}
r := ring.New(connectionServerKeepCertificates)
for i := 0; i < connectionServerKeepCertificates; i++ {
r.Value = cert
r = r.Next()
}
connectionServerInstance.nextWriteItem = r
connectionServerInstance.nextReadItem = r
go connectionServerInstance.run(connectionServerUpdateEvery)
})
}
-10
View File
@@ -1,10 +0,0 @@
package faketls
import (
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
)
func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) {
return nil, nil
}
+1
View File
@@ -17,6 +17,7 @@ require (
go.uber.org/atomic v1.4.0 // indirect go.uber.org/atomic v1.4.0 // indirect
go.uber.org/multierr v1.2.0 // indirect go.uber.org/multierr v1.2.0 // indirect
go.uber.org/zap v1.10.0 go.uber.org/zap v1.10.0
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/net v0.0.0-20191009170851-d66e71096ffb // indirect golang.org/x/net v0.0.0-20191009170851-d66e71096ffb // indirect
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 golang.org/x/sys v0.0.0-20191010194322-b09406accb47
gopkg.in/alecthomas/kingpin.v2 v2.2.6 gopkg.in/alecthomas/kingpin.v2 v2.2.6
+1
View File
@@ -109,6 +109,7 @@ go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+4 -2
View File
@@ -13,6 +13,7 @@ import (
"github.com/9seconds/mtg/config" "github.com/9seconds/mtg/config"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/stats"
"github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers/stream" "github.com/9seconds/mtg/wrappers/stream"
) )
@@ -81,11 +82,12 @@ func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conn
} }
antiReplayKey := decryptedFrame.Unique() antiReplayKey := decryptedFrame.Unique()
if antireplay.Cache.Has(antiReplayKey) { if antireplay.Cache.HasObfuscated2(antiReplayKey) {
stats.Stats.AntiReplayDetected()
return nil, errors.New("replay attack is detected") return nil, errors.New("replay attack is detected")
} }
antireplay.Cache.Add(antiReplayKey) antireplay.Cache.AddObfuscated2(antiReplayKey)
return stream.NewObfuscated2(socket, encryptor, decryptor), nil return stream.NewObfuscated2(socket, encryptor, decryptor), nil
} }
+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
})
}
+19
View File
@@ -1,5 +1,10 @@
package utils package utils
import (
"fmt"
"strings"
)
type Uint24 [3]byte type Uint24 [3]byte
func ToUint24(number uint32) Uint24 { func ToUint24(number uint32) Uint24 {
@@ -9,3 +14,17 @@ func ToUint24(number uint32) Uint24 {
func FromUint24(number Uint24) uint32 { func FromUint24(number Uint24) uint32 {
return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16) return uint32(number[0]) + (uint32(number[1]) << 8) + (uint32(number[2]) << 16)
} }
func Hexify(data []byte) string {
s := []string{}
for _, v := range data {
if v < 0x10 {
s = append(s, fmt.Sprintf("0x0%x", v))
} else {
s = append(s, fmt.Sprintf("0x%x", v))
}
}
return strings.Join(s, " ")
}
+14 -66
View File
@@ -1,28 +1,15 @@
package stream package stream
import ( import (
"bytes"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
) "github.com/9seconds/mtg/tlstypes"
var (
errFakeTLSTimeout = errors.New("timeout")
fakeTLSWritePrefix = []byte{0x17, 0x03, 0x03}
)
const (
faketlsMaxChunkSize = 16384 + 24
faketlsRecordTypeApplicationData = 0x17
faketlsRecordTypeCCS = 0x14
) )
type wrapperFakeTLS struct { type wrapperFakeTLS struct {
@@ -45,38 +32,20 @@ func (w *wrapperFakeTLS) WriteTimeout(p []byte, timeout time.Duration) (int, err
if elapsed > timeout { if elapsed > timeout {
return w.parent.WriteTimeout(b, timeout-elapsed) return w.parent.WriteTimeout(b, timeout-elapsed)
} }
return 0, errFakeTLSTimeout return 0, errors.New("timeout")
}) })
} }
func (w *wrapperFakeTLS) write(p []byte, writeFunc func([]byte) (int, error)) (int, error) { func (w *wrapperFakeTLS) write(p []byte, writeFunc func([]byte) (int, error)) (int, error) {
sum := 0 sum := 0
size := [2]byte{}
for len(p) > 0 {
chunkSize := faketlsMaxChunkSize
if chunkSize > len(p) {
chunkSize = len(p)
}
if _, err := writeFunc(fakeTLSWritePrefix); err != nil {
return sum, err
}
binary.BigEndian.PutUint16(size[:], uint16(chunkSize))
if _, err := writeFunc(size[:]); err != nil {
return sum, err
}
n, err := writeFunc(p[:chunkSize])
sum += n
for _, v := range tlstypes.MakeRecords(p) {
_, err := writeFunc(v.Bytes())
if err != nil { if err != nil {
return sum, err return sum, err
} }
p = p[chunkSize:] sum += len(v.Data.Bytes())
} }
return sum, nil return sum, nil
@@ -108,41 +77,20 @@ func NewFakeTLS(socket conntypes.StreamReadWriteCloser) conntypes.StreamReadWrit
} }
faketls.readFunc = func() ([]byte, error) { faketls.readFunc = func() ([]byte, error) {
data := &bytes.Buffer{} for {
buf := [2]byte{} rec, err := tlstypes.ReadRecord(faketls.parent)
recordType := byte(faketlsRecordTypeCCS) if err != nil {
return nil, err
for recordType == faketlsRecordTypeCCS {
if _, err := io.ReadFull(faketls.parent, buf[:1]); err != nil {
return nil, fmt.Errorf("cannot read record type: %w", err)
} }
switch buf[0] { switch rec.Type {
case faketlsRecordTypeCCS, faketlsRecordTypeApplicationData: case tlstypes.RecordTypeChangeCipherSpec:
recordType = buf[0] case tlstypes.RecordTypeApplicationData:
return rec.Data.Bytes(), nil
default: default:
return nil, fmt.Errorf("incorrect record type %v", buf[0]) return nil, fmt.Errorf("unsupported record type %v", rec.Type)
}
if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil {
return nil, fmt.Errorf("cannot read version: %w", err)
}
if !bytes.Equal(buf[:], []byte{0x03, 0x03}) {
return nil, fmt.Errorf("unknown tls version %v", buf)
}
if _, err := io.ReadFull(faketls.parent, buf[:]); err != nil {
return nil, fmt.Errorf("cannot read data length: %w", err)
}
dataLength := binary.BigEndian.Uint16(buf[:])
if _, err := io.CopyN(data, faketls.parent, int64(dataLength)); err != nil {
return nil, fmt.Errorf("cannot copy frame data: %w", err)
} }
} }
return data.Bytes(), nil
} }
return faketls return faketls
+8 -2
View File
@@ -43,7 +43,10 @@ func (w *wrapperRewind) Read(p []byte) (int, error) {
} }
n, err := w.parent.Read(p) n, err := w.parent.Read(p)
w.buf.Write(p[:n])
if !w.rewinded {
w.buf.Write(p[:n])
}
return n, err return n, err
} }
@@ -59,7 +62,10 @@ func (w *wrapperRewind) ReadTimeout(p []byte, timeout time.Duration) (int, error
} }
n, err := w.parent.ReadTimeout(p, timeout) n, err := w.parent.ReadTimeout(p, timeout)
w.buf.Write(p[:n])
if !w.rewinded {
w.buf.Write(p[:n])
}
return n, err return n, err
} }