mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 09:54:01 +03:00
Remove old faketls package
This commit is contained in:
@@ -1,134 +0,0 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
)
|
||||
|
||||
type ClientHello struct {
|
||||
Time time.Time
|
||||
Random [RandomLen]byte
|
||||
SessionID []byte
|
||||
Host string
|
||||
CipherSuite uint16
|
||||
}
|
||||
|
||||
func (c ClientHello) Valid(hostname string, tolerateTimeSkewness time.Duration) error {
|
||||
if c.Host != "" && c.Host != hostname {
|
||||
return fmt.Errorf("incorrect hostname %s", hostname)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
timeDiff := now.Sub(c.Time)
|
||||
if timeDiff < 0 {
|
||||
timeDiff = -timeDiff
|
||||
}
|
||||
|
||||
if timeDiff > tolerateTimeSkewness {
|
||||
return fmt.Errorf("incorrect timestamp. got=%d, now=%d, diff=%s",
|
||||
c.Time.Unix(), now.Unix(), timeDiff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseClientHello(secret, handshake []byte) (ClientHello, error) {
|
||||
hello := ClientHello{}
|
||||
|
||||
if len(handshake) < ClientHelloMinLen {
|
||||
return hello, fmt.Errorf("lengh of handshake is too small: %d", len(handshake))
|
||||
}
|
||||
|
||||
if handshake[0] != HandshakeTypeClient {
|
||||
return hello, fmt.Errorf("unknown handshake type %#x", handshake[0])
|
||||
}
|
||||
|
||||
handshakeSizeBytes := [4]byte{0, handshake[1], handshake[2], handshake[3]}
|
||||
handshakeLength := binary.BigEndian.Uint32(handshakeSizeBytes[:])
|
||||
|
||||
if len(handshake)-4 != int(handshakeLength) {
|
||||
return hello,
|
||||
fmt.Errorf("incorrect handshake size. manifested=%d, real=%d",
|
||||
handshakeLength, len(handshake)-4)
|
||||
}
|
||||
|
||||
copy(hello.Random[:], handshake[ClientHelloRandomOffset:])
|
||||
copy(handshake[ClientHelloRandomOffset:], clientHelloEmptyRandom)
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
rec.Type = record.TypeHandshake
|
||||
rec.Version = record.Version10
|
||||
rec.Payload.Write(handshake)
|
||||
|
||||
// mac is calculated for the whole record, not only
|
||||
// for the payload part
|
||||
mac := hmac.New(sha256.New, secret)
|
||||
rec.Dump(mac) //nolint: errcheck
|
||||
|
||||
computedRandom := mac.Sum(nil)
|
||||
|
||||
for i := range RandomLen {
|
||||
computedRandom[i] ^= hello.Random[i]
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(clientHelloEmptyRandom[:RandomLen-4], computedRandom[:RandomLen-4]) != 1 {
|
||||
return hello, ErrBadDigest
|
||||
}
|
||||
|
||||
timestamp := int64(binary.LittleEndian.Uint32(computedRandom[RandomLen-4:]))
|
||||
hello.Time = time.Unix(timestamp, 0)
|
||||
|
||||
parseSessionID(&hello, handshake)
|
||||
parseCipherSuite(&hello, handshake)
|
||||
parseSNI(&hello, handshake)
|
||||
|
||||
return hello, nil
|
||||
}
|
||||
|
||||
func parseSessionID(hello *ClientHello, handshake []byte) {
|
||||
hello.SessionID = make([]byte, handshake[ClientHelloSessionIDOffset])
|
||||
copy(hello.SessionID, handshake[ClientHelloSessionIDOffset+1:])
|
||||
}
|
||||
|
||||
func parseCipherSuite(hello *ClientHello, handshake []byte) {
|
||||
cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 3
|
||||
hello.CipherSuite = binary.BigEndian.Uint16(handshake[cipherSuiteOffset : cipherSuiteOffset+2])
|
||||
}
|
||||
|
||||
func parseSNI(hello *ClientHello, handshake []byte) {
|
||||
cipherSuiteOffset := ClientHelloSessionIDOffset + len(hello.SessionID) + 1
|
||||
handshake = handshake[cipherSuiteOffset:]
|
||||
|
||||
cipherSuiteLength := binary.BigEndian.Uint16(handshake[:2])
|
||||
handshake = handshake[2+cipherSuiteLength:]
|
||||
|
||||
compressionMethodsLength := int(handshake[0])
|
||||
handshake = handshake[1+compressionMethodsLength:]
|
||||
|
||||
extensionsLength := binary.BigEndian.Uint16(handshake[:2])
|
||||
handshake = handshake[2 : 2+extensionsLength]
|
||||
|
||||
for len(handshake) > 0 {
|
||||
if binary.BigEndian.Uint16(handshake[:2]) != ExtensionSNI {
|
||||
extensionsLength := binary.BigEndian.Uint16(handshake[2:4])
|
||||
handshake = handshake[4+extensionsLength:]
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
hostnameLength := binary.BigEndian.Uint16(handshake[7:9])
|
||||
handshake = handshake[9:]
|
||||
hello.Host = string(handshake[:int(hostnameLength)])
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package faketls_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var FuzzClientHelloSecret = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
|
||||
func FuzzClientHello(f *testing.F) {
|
||||
f.Add([]byte{1, 2, 3})
|
||||
|
||||
f.Fuzz(func(t *testing.T, frame []byte) {
|
||||
_, err := faketls.ParseClientHello(FuzzClientHelloSecret, frame)
|
||||
|
||||
// a probability of having != err is almost negligible
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package faketls_test
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ClientHelloSnapshot struct {
|
||||
Time int `json:"time"`
|
||||
Random string `json:"random"`
|
||||
SessionID string `json:"sessionId"`
|
||||
Host string `json:"host"`
|
||||
CipherSuite int `json:"cipherSuite"`
|
||||
Full string `json:"full"`
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetTime() time.Time {
|
||||
return time.Unix(int64(c.Time), 0)
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetRandom() []byte {
|
||||
data, _ := base64.StdEncoding.DecodeString(c.Random)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetSessionID() []byte {
|
||||
data, _ := base64.StdEncoding.DecodeString(c.SessionID)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetHost() string {
|
||||
return c.Host
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetCipherSuite() uint16 {
|
||||
return uint16(c.CipherSuite)
|
||||
}
|
||||
|
||||
func (c ClientHelloSnapshot) GetFull() []byte {
|
||||
data, _ := base64.StdEncoding.DecodeString(c.Full)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
type ClientHelloTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
secret mtglib.Secret
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) SetupSuite() {
|
||||
parsed, err := mtglib.ParseSecret("ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
suite.secret = parsed
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestEmptyHandshake() {
|
||||
_, err := faketls.ParseClientHello(suite.secret.Key[:], nil)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestIncorrectHandshakeType() {
|
||||
data := make([]byte, 1024)
|
||||
data[0] = 0x02
|
||||
|
||||
_, err := faketls.ParseClientHello(suite.secret.Key[:], data)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestIncorrectLength() {
|
||||
data := make([]byte, 1024)
|
||||
data[0] = 0x01
|
||||
data[1] = 0xff
|
||||
data[2] = 0xff
|
||||
|
||||
_, err := faketls.ParseClientHello(suite.secret.Key[:], data)
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestSnapshotOk() {
|
||||
files, err := os.ReadDir("testdata")
|
||||
suite.NoError(err)
|
||||
|
||||
testData := []string{}
|
||||
|
||||
for _, v := range files {
|
||||
if strings.HasPrefix(v.Name(), "client-hello-ok") {
|
||||
testData = append(testData, v.Name())
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range testData {
|
||||
path := filepath.Join("testdata", name)
|
||||
|
||||
suite.T().Run(name, func(t *testing.T) {
|
||||
fileData, err := os.ReadFile(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot := &ClientHelloSnapshot{}
|
||||
assert.NoError(t, json.Unmarshal(fileData, snapshot))
|
||||
|
||||
hello, err := faketls.ParseClientHello(suite.secret.Key[:], snapshot.GetFull())
|
||||
assert.NoError(t, err)
|
||||
assert.WithinDuration(t, snapshot.GetTime(), hello.Time, time.Second)
|
||||
assert.Equal(t, snapshot.GetRandom(), hello.Random[:])
|
||||
assert.Equal(t, snapshot.GetSessionID(), hello.SessionID)
|
||||
assert.Equal(t, snapshot.GetHost(), hello.Host)
|
||||
assert.Equal(t, snapshot.GetCipherSuite(), hello.CipherSuite)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestSnapshotBad() {
|
||||
files, err := os.ReadDir("testdata")
|
||||
suite.NoError(err)
|
||||
|
||||
testData := []string{}
|
||||
|
||||
for _, v := range files {
|
||||
if strings.HasPrefix(v.Name(), "client-hello-bad") {
|
||||
testData = append(testData, v.Name())
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range testData {
|
||||
path := filepath.Join("testdata", name)
|
||||
|
||||
suite.T().Run(name, func(t *testing.T) {
|
||||
fileData, err := os.ReadFile(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot := &ClientHelloSnapshot{}
|
||||
assert.NoError(t, json.Unmarshal(fileData, snapshot))
|
||||
|
||||
_, err = faketls.ParseClientHello(suite.secret.Key[:], snapshot.GetFull())
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestValidateHostname() {
|
||||
hello := faketls.ClientHello{
|
||||
Time: time.Now(),
|
||||
}
|
||||
suite.NoError(hello.Valid("hostname", time.Second))
|
||||
|
||||
hello.Host = "hostname"
|
||||
suite.Error(hello.Valid("hostname2", time.Second))
|
||||
suite.NoError(hello.Valid("hostname", time.Second))
|
||||
}
|
||||
|
||||
func (suite *ClientHelloTestSuite) TestValidateTime() {
|
||||
testData := []time.Duration{
|
||||
-2 * time.Second,
|
||||
2 * time.Second,
|
||||
}
|
||||
|
||||
for _, v := range testData {
|
||||
value := v
|
||||
|
||||
suite.T().Run(value.String(), func(t *testing.T) {
|
||||
hello := faketls.ClientHello{
|
||||
Host: "hostname",
|
||||
Time: time.Now().Add(value),
|
||||
}
|
||||
suite.Error(hello.Valid("hostname", 500*time.Millisecond))
|
||||
suite.Error(hello.Valid("hostname", time.Second))
|
||||
suite.NoError(hello.Valid("hostname", 3*time.Second))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientHello(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &ClientHelloTestSuite{})
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
|
||||
"github.com/9seconds/mtg/v2/essentials"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
essentials.Conn
|
||||
|
||||
readBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
func (c *Conn) Read(p []byte) (int, error) {
|
||||
if n, _ := c.readBuffer.Read(p); n > 0 {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
for {
|
||||
if err := rec.Read(c.Conn); err != nil {
|
||||
return 0, err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
switch rec.Type { //nolint: exhaustive
|
||||
case record.TypeApplicationData:
|
||||
rec.Payload.WriteTo(&c.readBuffer) //nolint: errcheck
|
||||
|
||||
return c.readBuffer.Read(p) //nolint: wrapcheck
|
||||
case record.TypeChangeCipherSpec:
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported record type %v", rec.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) Write(p []byte) (int, error) {
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
rec.Type = record.TypeApplicationData
|
||||
rec.Version = record.Version12
|
||||
|
||||
written := 0
|
||||
|
||||
for len(p) > 0 {
|
||||
chunkSize := rand.IntN(record.TLSMaxRecordSize)
|
||||
if chunkSize > len(p) || chunkSize == 0 {
|
||||
chunkSize = len(p)
|
||||
}
|
||||
|
||||
rec.Payload.Reset()
|
||||
rec.Payload.Write(p[:chunkSize])
|
||||
|
||||
err := rec.Dump(c.Conn)
|
||||
written += chunkSize
|
||||
|
||||
if err != nil {
|
||||
return written, err
|
||||
}
|
||||
|
||||
p = p[chunkSize:]
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package faketls_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/internal/testlib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ConnMock struct {
|
||||
testlib.EssentialsConnMock
|
||||
|
||||
readBuffer bytes.Buffer
|
||||
writeBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
func (m *ConnMock) Read(p []byte) (int, error) {
|
||||
m.Called(p)
|
||||
|
||||
return m.readBuffer.Read(p) //nolint: wrapcheck
|
||||
}
|
||||
|
||||
func (m *ConnMock) Write(p []byte) (int, error) {
|
||||
m.Called(p)
|
||||
|
||||
return m.writeBuffer.Write(p) //nolint: wrapcheck
|
||||
}
|
||||
|
||||
type ConnTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
connMock *ConnMock
|
||||
c *faketls.Conn
|
||||
}
|
||||
|
||||
func (suite *ConnTestSuite) SetupTest() {
|
||||
suite.connMock = &ConnMock{}
|
||||
suite.c = &faketls.Conn{
|
||||
Conn: suite.connMock,
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ConnTestSuite) TearDownTest() {
|
||||
suite.connMock.AssertExpectations(suite.T())
|
||||
}
|
||||
|
||||
func (suite *ConnTestSuite) TestRead() {
|
||||
suite.connMock.On("Read", mock.Anything).Return(0, nil)
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
rec.Type = record.TypeChangeCipherSpec
|
||||
rec.Version = record.Version12
|
||||
|
||||
rec.Payload.WriteByte(0x01)
|
||||
rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
|
||||
rec.Reset()
|
||||
|
||||
rec.Type = record.TypeApplicationData
|
||||
rec.Version = record.Version12
|
||||
|
||||
rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
|
||||
|
||||
resultBuffer := &bytes.Buffer{}
|
||||
buf := make([]byte, 2)
|
||||
|
||||
for {
|
||||
n, err := suite.c.Read(buf)
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
resultBuffer.Write(buf[:n])
|
||||
}
|
||||
|
||||
suite.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, resultBuffer.Bytes())
|
||||
}
|
||||
|
||||
func (suite *ConnTestSuite) TestReadUnexpected() {
|
||||
suite.connMock.On("Read", mock.Anything).Return(0, nil)
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
rec.Type = record.TypeChangeCipherSpec
|
||||
rec.Version = record.Version12
|
||||
|
||||
rec.Payload.WriteByte(0x01)
|
||||
rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
|
||||
rec.Reset()
|
||||
|
||||
rec.Type = record.TypeHandshake
|
||||
rec.Version = record.Version12
|
||||
|
||||
rec.Payload.Write([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
|
||||
rec.Dump(&suite.connMock.readBuffer) //nolint: errcheck
|
||||
|
||||
buf := make([]byte, 2)
|
||||
|
||||
for {
|
||||
_, err := suite.c.Read(buf)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
case errors.Is(err, io.EOF):
|
||||
suite.FailNow("unexpected to finish")
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *ConnTestSuite) TestWrite() {
|
||||
suite.connMock.On("Write", mock.Anything).Return(0, nil)
|
||||
|
||||
dataToRec := make([]byte, record.TLSMaxRecordSize*2)
|
||||
rand.Read(dataToRec) //nolint: staticcheck, errcheck
|
||||
|
||||
n, err := suite.c.Write(dataToRec)
|
||||
suite.NoError(err)
|
||||
suite.Equal(len(dataToRec), n)
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for {
|
||||
if err := rec.Read(&suite.connMock.writeBuffer); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
suite.Equal(record.TypeApplicationData, rec.Type)
|
||||
suite.Equal(record.Version12, rec.Version)
|
||||
rec.Payload.WriteTo(buf) //nolint: errcheck
|
||||
}
|
||||
|
||||
suite.Equal(dataToRec, buf.Bytes())
|
||||
}
|
||||
|
||||
func TestConn(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &ConnTestSuite{})
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// RandomLen defines a size of the random digest in TLS Hellos.
|
||||
RandomLen = 32
|
||||
|
||||
// ClientHelloRandomOffset is an offset in ClientHello record where
|
||||
// random digest is started.
|
||||
ClientHelloRandomOffset = 6
|
||||
|
||||
// ClientHelloSessionIDOffset is an offset in ClientHello record where
|
||||
// SessionID is started.
|
||||
ClientHelloSessionIDOffset = ClientHelloRandomOffset + RandomLen
|
||||
|
||||
// ClientHelloMinLen is a minimal possible length of
|
||||
// ClientHello record.
|
||||
ClientHelloMinLen = 6
|
||||
|
||||
// WelcomePacketRandomOffset is an offset of random in ServerHello
|
||||
// packet (including record envelope).
|
||||
WelcomePacketRandomOffset = 11
|
||||
|
||||
// HandshakeTypeClient is a value representing a client handshake.
|
||||
HandshakeTypeClient = 0x01
|
||||
|
||||
// HandshakeTypeServer is a value representing a server handshake.
|
||||
HandshakeTypeServer = 0x02
|
||||
|
||||
// ChangeCipherValue is a value representing a change cipher
|
||||
// specification record.
|
||||
ChangeCipherValue = 0x01
|
||||
|
||||
// ExtensionSNI is a value for TLS extension 'SNI'.
|
||||
ExtensionSNI = 0x00
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrBadDigest is returned if given TLS Client Hello mismatches with a
|
||||
// derived one.
|
||||
ErrBadDigest = errors.New("bad digest")
|
||||
|
||||
serverHelloSuffix = []byte{
|
||||
0x00, // no compression
|
||||
0x00, 0x2e, // 46 bytes of data
|
||||
0x00, 0x2b, // Extension - Supported Versions
|
||||
0x00, 0x02, // 2 bytes are following
|
||||
0x03, 0x04, // TLS 1.3
|
||||
0x00, 0x33, // Extension - Key Share
|
||||
0x00, 0x24, // 36 bytes
|
||||
0x00, 0x1d, // x25519 curve
|
||||
0x00, 0x20, // 32 bytes of key
|
||||
}
|
||||
clientHelloEmptyRandom = bytes.Repeat([]byte{0}, RandomLen)
|
||||
)
|
||||
@@ -1,84 +0,0 @@
|
||||
package record
|
||||
|
||||
import "fmt"
|
||||
|
||||
const TLSMaxRecordSize = 65535 // max uint16
|
||||
|
||||
type Type uint8
|
||||
|
||||
const (
|
||||
// TypeChangeCipherSpec defines a byte value of the TLS record when a
|
||||
// peer wants to change a specifications of the chosen cipher.
|
||||
TypeChangeCipherSpec Type = 0x14
|
||||
|
||||
// TypeHandshake defines a byte value of the TLS record when a peer
|
||||
// initiates a new TLS connection and wants to make a handshake
|
||||
// ceremony.
|
||||
TypeHandshake Type = 0x16
|
||||
|
||||
// TypeApplicationData defines a byte value of the TLS record when a
|
||||
// peer sends an user data, not a control frames.
|
||||
TypeApplicationData Type = 0x17
|
||||
)
|
||||
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeChangeCipherSpec:
|
||||
return "changeCipher(0x14)"
|
||||
case TypeHandshake:
|
||||
return "handshake(0x16)"
|
||||
case TypeApplicationData:
|
||||
return "applicationData(0x17)"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("unknown(%#x)", byte(t))
|
||||
}
|
||||
|
||||
func (t Type) Valid() error {
|
||||
switch t {
|
||||
case TypeChangeCipherSpec, TypeHandshake, TypeApplicationData:
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown type %#x", byte(t))
|
||||
}
|
||||
|
||||
type Version uint16
|
||||
|
||||
const (
|
||||
// Version10 defines a TLS1.0.
|
||||
Version10 Version = 769 // 0x03 0x01
|
||||
|
||||
// Version11 defines a TLS1.1.
|
||||
Version11 Version = 770 // 0x03 0x02
|
||||
|
||||
// Version12 defines a TLS1.2.
|
||||
Version12 Version = 771 // 0x03 0x03
|
||||
|
||||
// Version13 defines a TLS1.3.
|
||||
Version13 Version = 772 // 0x03 0x04
|
||||
)
|
||||
|
||||
func (v Version) String() string {
|
||||
switch v {
|
||||
case Version10:
|
||||
return "tls1.0"
|
||||
case Version11:
|
||||
return "tls1.1"
|
||||
case Version12:
|
||||
return "tls1.2"
|
||||
case Version13:
|
||||
return "tls1.3"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("tls?(%d)", uint16(v))
|
||||
}
|
||||
|
||||
func (v Version) Valid() error {
|
||||
switch v {
|
||||
case Version10, Version11, Version12, Version13:
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown version %d", uint16(v))
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package record_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type TypeTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *TypeTestSuite) TestChangeCipherSpec() {
|
||||
suite.Contains(record.TypeChangeCipherSpec.String(), "changeCipher")
|
||||
suite.Contains(record.TypeChangeCipherSpec.String(), "0x14")
|
||||
suite.NoError(record.TypeChangeCipherSpec.Valid())
|
||||
}
|
||||
|
||||
func (suite *TypeTestSuite) TestHandshake() {
|
||||
suite.Contains(record.TypeHandshake.String(), "handshake")
|
||||
suite.Contains(record.TypeHandshake.String(), "0x16")
|
||||
suite.NoError(record.TypeHandshake.Valid())
|
||||
}
|
||||
|
||||
func (suite *TypeTestSuite) TestApplicationData() {
|
||||
suite.Contains(record.TypeApplicationData.String(), "applicationData")
|
||||
suite.Contains(record.TypeApplicationData.String(), "0x17")
|
||||
suite.NoError(record.TypeApplicationData.Valid())
|
||||
}
|
||||
|
||||
func (suite *TypeTestSuite) TestUnknown() {
|
||||
value := record.Type(0x20)
|
||||
|
||||
suite.Contains(value.String(), "unknown")
|
||||
suite.Contains(value.String(), "0x20")
|
||||
suite.Error(value.Valid())
|
||||
}
|
||||
|
||||
type VersionTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *VersionTestSuite) Test10() {
|
||||
suite.Equal("tls1.0", record.Version10.String())
|
||||
suite.NoError(record.Version10.Valid())
|
||||
}
|
||||
|
||||
func (suite *VersionTestSuite) Test11() {
|
||||
suite.Equal("tls1.1", record.Version11.String())
|
||||
suite.NoError(record.Version11.Valid())
|
||||
}
|
||||
|
||||
func (suite *VersionTestSuite) Test12() {
|
||||
suite.Equal("tls1.2", record.Version12.String())
|
||||
suite.NoError(record.Version12.Valid())
|
||||
}
|
||||
|
||||
func (suite *VersionTestSuite) Test13() {
|
||||
suite.Equal("tls1.3", record.Version13.String())
|
||||
suite.NoError(record.Version13.Valid())
|
||||
}
|
||||
|
||||
func (suite *VersionTestSuite) TestUnknown() {
|
||||
value := record.Version(900)
|
||||
|
||||
suite.Equal("tls?(900)", value.String())
|
||||
suite.Error(value.Valid())
|
||||
}
|
||||
|
||||
func TestType(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &TypeTestSuite{})
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &VersionTestSuite{})
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var recordPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &Record{}
|
||||
},
|
||||
}
|
||||
|
||||
func AcquireRecord() *Record {
|
||||
return recordPool.Get().(*Record) //nolint: forcetypeassert
|
||||
}
|
||||
|
||||
func ReleaseRecord(r *Record) {
|
||||
r.Reset()
|
||||
recordPool.Put(r)
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package record
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
Type Type
|
||||
Version Version
|
||||
Payload bytes.Buffer
|
||||
}
|
||||
|
||||
func (r *Record) String() string {
|
||||
return fmt.Sprintf("<tlsRecord(type=%v, version=%v, payload=%s)>",
|
||||
r.Type,
|
||||
r.Version,
|
||||
base64.StdEncoding.EncodeToString(r.Payload.Bytes()))
|
||||
}
|
||||
|
||||
func (r *Record) Reset() {
|
||||
r.Payload.Reset()
|
||||
}
|
||||
|
||||
func (r *Record) Read(reader io.Reader) error {
|
||||
r.Reset()
|
||||
|
||||
buf := [2]byte{}
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:1]); err != nil {
|
||||
return fmt.Errorf("cannot read type: %w", err)
|
||||
}
|
||||
|
||||
r.Type = Type(buf[0])
|
||||
if err := r.Type.Valid(); err != nil {
|
||||
return fmt.Errorf("invalid type: %w", err)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||
return fmt.Errorf("cannot read version: %w", err)
|
||||
}
|
||||
|
||||
r.Version = Version(binary.BigEndian.Uint16(buf[:]))
|
||||
if err := r.Version.Valid(); err != nil {
|
||||
return fmt.Errorf("invalid version: %w", err)
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(reader, buf[:]); err != nil {
|
||||
return fmt.Errorf("cannot read payload length: %w", err)
|
||||
}
|
||||
|
||||
length := int64(binary.BigEndian.Uint16(buf[:]))
|
||||
if _, err := io.CopyN(&r.Payload, reader, length); err != nil {
|
||||
return fmt.Errorf("cannot read payload: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Record) Dump(writer io.Writer) error {
|
||||
buf := [2]byte{byte(r.Type), 0}
|
||||
if _, err := writer.Write(buf[:1]); err != nil {
|
||||
return fmt.Errorf("cannot dump record type: %w", err)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:], uint16(r.Version))
|
||||
|
||||
if _, err := writer.Write(buf[:]); err != nil {
|
||||
return fmt.Errorf("cannot dump version: %w", err)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(buf[:], uint16(r.Payload.Len()))
|
||||
|
||||
if _, err := writer.Write(buf[:]); err != nil {
|
||||
return fmt.Errorf("cannot dump payload length: %w", err)
|
||||
}
|
||||
|
||||
if _, err := writer.Write(r.Payload.Bytes()); err != nil {
|
||||
return fmt.Errorf("cannot dump record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package record_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type RecordTestSnapshot struct {
|
||||
Type int `json:"type"`
|
||||
Version int `json:"version"`
|
||||
Payload string `json:"payload"`
|
||||
Record string `json:"record"`
|
||||
}
|
||||
|
||||
func (r RecordTestSnapshot) RecordBytes() []byte {
|
||||
data, _ := base64.StdEncoding.DecodeString(r.Record)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (r RecordTestSnapshot) PayloadBytes() []byte {
|
||||
data, _ := base64.StdEncoding.DecodeString(r.Payload)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
type RecordTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
r *record.Record
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (suite *RecordTestSuite) SetupTest() {
|
||||
suite.r = record.AcquireRecord()
|
||||
suite.buf = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
func (suite *RecordTestSuite) TearDownTest() {
|
||||
record.ReleaseRecord(suite.r)
|
||||
suite.buf.Reset()
|
||||
}
|
||||
|
||||
func (suite *RecordTestSuite) TestIdempotent() {
|
||||
suite.r.Type = record.TypeApplicationData
|
||||
suite.r.Version = record.Version13
|
||||
|
||||
suite.r.Payload.Write([]byte{1, 2, 3})
|
||||
suite.NoError(suite.r.Dump(suite.buf))
|
||||
|
||||
suite.r.Reset()
|
||||
suite.NoError(suite.r.Read(suite.buf))
|
||||
|
||||
suite.Equal(0, suite.buf.Len())
|
||||
suite.Equal(record.TypeApplicationData, suite.r.Type)
|
||||
suite.Equal(record.Version13, suite.r.Version)
|
||||
suite.Equal([]byte{1, 2, 3}, suite.r.Payload.Bytes())
|
||||
}
|
||||
|
||||
func (suite *RecordTestSuite) TestString() {
|
||||
_ = suite.r.String()
|
||||
}
|
||||
|
||||
func (suite *RecordTestSuite) TestSnapshot() {
|
||||
files, err := os.ReadDir("testdata")
|
||||
suite.NoError(err)
|
||||
|
||||
testData := map[string]string{}
|
||||
|
||||
for _, f := range files {
|
||||
testData[f.Name()] = filepath.Join("testdata", f.Name())
|
||||
}
|
||||
|
||||
for name, pathV := range testData {
|
||||
path := pathV
|
||||
|
||||
suite.T().Run(name, func(t *testing.T) {
|
||||
data, err := os.ReadFile(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot := &RecordTestSnapshot{}
|
||||
assert.NoError(t, json.Unmarshal(data, snapshot))
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
assert.NoError(t, rec.Read(bytes.NewReader(snapshot.RecordBytes())))
|
||||
assert.Equal(t, snapshot.Type, int(rec.Type))
|
||||
assert.Equal(t, snapshot.Version, int(rec.Version))
|
||||
assert.Equal(t, snapshot.PayloadBytes(), rec.Payload.Bytes())
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
assert.NoError(t, rec.Dump(buf))
|
||||
assert.Equal(t, snapshot.RecordBytes(), buf.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &RecordTestSuite{})
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 20,
|
||||
"version": 772,
|
||||
"payload": "sxS+0oAyk+NBv0LLVtQOp9WSx4CweyUZPz01tQ0o4oyp8aaBl6/kMFvLq3q52KE8lCiKejLw2NxVBUkE+4izCf2gLx9qfr81opWnqJTChWzcDijvttbq9cmtDFNL+odKsS3v1/TfYEFtPsoRPrJRmOHRAnqnf49Y5Q==",
|
||||
"record": "FAMEAHmzFL7SgDKT40G/QstW1A6n1ZLHgLB7JRk/PTW1DSjijKnxpoGXr+QwW8urernYoTyUKIp6MvDY3FUFSQT7iLMJ/aAvH2p+vzWilaeolMKFbNwOKO+21ur1ya0MU0v6h0qxLe/X9N9gQW0+yhE+slGY4dECeqd/j1jl"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 22,
|
||||
"version": 772,
|
||||
"payload": "waNH223htyxCBKAb6hm0u/SK/9mhI8Ck91nfWob7QMOaIREogrDYREJH4Djcp47XrpAlEaUIDiCvoFLVJ/LK1nYs4swzfHSSl/+Aj1eqPA63XqPa8EG4FAbf0DwjwXxV9qVIhvP9b2TafKbzr4Yb5GCygzFRb/zawA==",
|
||||
"record": "FgMEAHnBo0fbbeG3LEIEoBvqGbS79Ir/2aEjwKT3Wd9ahvtAw5ohESiCsNhEQkfgONynjteukCURpQgOIK+gUtUn8srWdizizDN8dJKX/4CPV6o8Drdeo9rwQbgUBt/QPCPBfFX2pUiG8/1vZNp8pvOvhhvkYLKDMVFv/NrA"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 23,
|
||||
"version": 769,
|
||||
"payload": "jmJ0o1E5+ehAHHYAbCo4AMV03X7RSivYl250s06nD9CO44fyjaoGELz0N7IeCg1jFKcRVSCRmYYmiIY9wydn2fXOJhKif8B0BlM3qhbethYgyP+l1S8hyyETpIiOtiiiOnAJwl1D1j9OryFiJFSdRRXReIMZ4CPqPg==",
|
||||
"record": "FwMBAHmOYnSjUTn56EAcdgBsKjgAxXTdftFKK9iXbnSzTqcP0I7jh/KNqgYQvPQ3sh4KDWMUpxFVIJGZhiaIhj3DJ2fZ9c4mEqJ/wHQGUzeqFt62FiDI/6XVLyHLIROkiI62KKI6cAnCXUPWP06vIWIkVJ1FFdF4gxngI+o+"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 22,
|
||||
"version": 769,
|
||||
"payload": "hBnpBnNUdlqe/rKXa7Judcz79u7AkUgSGOycn8EqvbkZpVxnI31rNOvAsPZqG+GF7DWJ3R7H2ETmFmrpnyyng32MjSs1jptmV1oAs63zTADD7sVipgid9AJHwfl4CrC3FIQr43IPMYd29JPOl5bqu/SfrgI16PBiJw==",
|
||||
"record": "FgMBAHmEGekGc1R2Wp7+spdrsm51zPv27sCRSBIY7JyfwSq9uRmlXGcjfWs068Cw9mob4YXsNYndHsfYROYWaumfLKeDfYyNKzWOm2ZXWgCzrfNMAMPuxWKmCJ30AkfB+XgKsLcUhCvjcg8xh3b0k86Xluq79J+uAjXo8GIn"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 23,
|
||||
"version": 770,
|
||||
"payload": "Vm/C+DO56czlbtR915aHzsugSyDtp8CtojF9w1jKY0efyyfcLrNuhNg/pZm3gQ7v2BBbL1UJ97v/RIjST+5gRIfg3bBN1BE9hkf+N2AYY2lHLi0yeInHB0zFWPeHscsDopDFadIi5KtC8HvbEMuK+kK8POVk5tN9UQ==",
|
||||
"record": "FwMCAHlWb8L4M7npzOVu1H3XlofOy6BLIO2nwK2iMX3DWMpjR5/LJ9wus26E2D+lmbeBDu/YEFsvVQn3u/9EiNJP7mBEh+DdsE3UET2GR/43YBhjaUcuLTJ4iccHTMVY94exywOikMVp0iLkq0Lwe9sQy4r6Qrw85WTm031R"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 22,
|
||||
"version": 770,
|
||||
"payload": "ajPzpsgk4gwm2stRQKbllvKRLdI7vmyaj1uxEJ/kKoQnQSPumdDNKD618U2Cq6PVd0/b+9YtH67Uzx1QxtpKuby5fUXqw06WUuDAQsmjq7F26EkE5FND6rQUjUPC+e1U0dF4TQzOUSS4IAkFQPAaVehUVTRxVWa/0g==",
|
||||
"record": "FgMCAHlqM/OmyCTiDCbay1FApuWW8pEt0ju+bJqPW7EQn+QqhCdBI+6Z0M0oPrXxTYKro9V3T9v71i0frtTPHVDG2kq5vLl9RerDTpZS4MBCyaOrsXboSQTkU0PqtBSNQ8L57VTR0XhNDM5RJLggCQVA8BpV6FRVNHFVZr/S"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 20,
|
||||
"version": 771,
|
||||
"payload": "d1Hiv1NYVgEDR9mtJyv9j8mg3dWqfUpeKfOsL+jzSDfVIxeDiJZFLDT50TjNW44/yEOVEX/Y/pk+wnc7E8aCEiwGwAvB+Insw1UCJ2ejt689VWLo2u4klGVKTHuOpUvdGVTc7Lo4FAt91KQSPLYB5iqxomjEv5e3Vg==",
|
||||
"record": "FAMDAHl3UeK/U1hWAQNH2a0nK/2PyaDd1ap9Sl4p86wv6PNIN9UjF4OIlkUsNPnROM1bjj/IQ5URf9j+mT7CdzsTxoISLAbAC8H4iezDVQInZ6O3rz1VYuja7iSUZUpMe46lS90ZVNzsujgUC33UpBI8tgHmKrGiaMS/l7dW"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 23,
|
||||
"version": 771,
|
||||
"payload": "wbdU1CbrzuAJDsh6CFjGyE+AFArJj/Wmsa2wtDyW0kRuE2vUO8gg+nXkg0kkoz0WnvQEOdaswfJIaVrloD78yoyeQVfBB+VUP/63vqn60v5ccaQEn0jLdxgLjiTAxKDQDxCTMRoLnFE2ZZf28zw+HfqpIxiOZs8LhQ==",
|
||||
"record": "FwMDAHnBt1TUJuvO4AkOyHoIWMbIT4AUCsmP9aaxrbC0PJbSRG4Ta9Q7yCD6deSDSSSjPRae9AQ51qzB8khpWuWgPvzKjJ5BV8EH5VQ//re+qfrS/lxxpASfSMt3GAuOJMDEoNAPEJMxGgucUTZll/bzPD4d+qkjGI5mzwuF"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 23,
|
||||
"version": 772,
|
||||
"payload": "qqnBMb1Af3zZt4DPHpVRuIiON9ODGJUNFicFjranORh67L/HI4D6HnHyycZFUSBOw2FjMBF6UialY8snOYaRKrQmQzuUNg1Ztq7yAZ+Lgj3TBarR6OMlYhEAY0Px9Xv1UuJ0YcvQx33gdM1skJ5HBR3yZvEKNJV1LA==",
|
||||
"record": "FwMEAHmqqcExvUB/fNm3gM8elVG4iI4304MYlQ0WJwWOtqc5GHrsv8cjgPoecfLJxkVRIE7DYWMwEXpSJqVjyyc5hpEqtCZDO5Q2DVm2rvIBn4uCPdMFqtHo4yViEQBjQ/H1e/VS4nRhy9DHfeB0zWyQnkcFHfJm8Qo0lXUs"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 20,
|
||||
"version": 769,
|
||||
"payload": "NEe735TuQFp7bWpFQhASas/e1XaySvus0ovXmkfCbFq334MyFHq2eDMadziXsfu/GfBjoYggvk0LgYUeoAkBNKR0dfSovjSndaqmIUonoWl+6sZObiGZkRIMwuY2q4Eaw4/iuDu/pZhjRW/iAIH+YH7cyk/1tgdJDg==",
|
||||
"record": "FAMBAHk0R7vflO5AWnttakVCEBJqz97VdrJK+6zSi9eaR8JsWrffgzIUerZ4Mxp3OJex+78Z8GOhiCC+TQuBhR6gCQE0pHR19Ki+NKd1qqYhSiehaX7qxk5uIZmREgzC5jargRrDj+K4O7+lmGNFb+IAgf5gftzKT/W2B0kO"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 22,
|
||||
"version": 771,
|
||||
"payload": "wrXjZrPm3OSyzO0klv6/G+z2PDloR/colS/RlWwQE31Vb2xm8YkEchDDKwlc/KPLD73qMoz3MQOQLtSLc8LhVYp+l7L9jz49yTaVKtBI5UuGbo09snsKxFCgCyYUBETKabATBQtiaEu/D8dmF4Yk/2ww4sEb8DwKLQ==",
|
||||
"record": "FgMDAHnCteNms+bc5LLM7SSW/r8b7PY8OWhH9yiVL9GVbBATfVVvbGbxiQRyEMMrCVz8o8sPveoyjPcxA5Au1ItzwuFVin6Xsv2PPj3JNpUq0EjlS4ZujT2yewrEUKALJhQERMppsBMFC2JoS78Px2YXhiT/bDDiwRvwPAot"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"type": 20,
|
||||
"version": 770,
|
||||
"payload": "OU5s8Sa11hpXWEarWzFlX55IZt3Eo+F4AMbQ/2RwB4rfHS/JNl8n63OR4oYs9QXw3RfCrYJuU9n6Xn+I/+7ZzAgZ0PbLSXW1PrLtttdfmhTErK90b49YEWdY9na4g++NMkKykwgXvY1hNxZIHX/qawEWJgxXUR3DdQ==",
|
||||
"record": "FAMCAHk5TmzxJrXWGldYRqtbMWVfnkhm3cSj4XgAxtD/ZHAHit8dL8k2Xyfrc5Hihiz1BfDdF8Ktgm5T2fpef4j/7tnMCBnQ9stJdbU+su2211+aFMSsr3Rvj1gRZ1j2driD740yQrKTCBe9jWE3Fkgdf+prARYmDFdRHcN1"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181365,
|
||||
"random": "XvCPc3aAbHbhRLv0kUmy6BfPZOGvsused5/HNsKXEPs=",
|
||||
"sessionId": "St2BZ2uHMFn3B2trD1jfdtpjoJOOg6JBeLhFcyCMCq4=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMDXvCPc3aAbHbhRLv0kUmy6BfPZOGvsused5/HNsKXEPsgSt2BZ2uHMFn3B2trD1jfdtpjoJOOg6JBeLhFcyCACq4ANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFANAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAgB/7oLx9JElIALsLJS91H2QNyU1H0osKwIUelVndsLyIALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181365,
|
||||
"random": "XvCPc3aAbHbhRLv0kUmy6BfPZOGvsused5/HNsKXEPs=",
|
||||
"sessionId": "St2BZ2uHMFn3B2trD1jfdtpjoJOOg6JBeLhFcyCMCq4=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMDXvCPc3aAbHbhRLv0kUmy6BfPZOGvsused5/HNsKXEPsgSt2BZ2uHMFn3B2trD1jfdtpjoJOOg6JBeLhFcyCMCq4ANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFAQAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAgB/7oLx9JElIALsLJS91H2QNyU1H0osKwIUelVndsLyIALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181352,
|
||||
"random": "oYEu33jl+zQbUKMtQbV1OHB0gXIM2y2aq9iY0QX12os=",
|
||||
"sessionId": "FGqA3ZFYrSlj//xl7lammNn64K9/MK2mQ3HJUGvP+8g=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMDoYEu33jl+zQbUKMtQbV1OHB0gXIM2y2aq9iY0QX12osgFGqA3ZFYrSlj//xl7lammNn64K9/MK2mQ3HJUGvP+8gANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFAQAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAga6CocpFP8Qd4YCFR9pkaCr97po2ALj0P5nI9Nnb3UWMALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181352,
|
||||
"random": "5V5sSprk/tFIgy+x1BeKNGhLlFkqfggLpgN7GYOA1ro=",
|
||||
"sessionId": "jxr4d6PXPDk+Lwx3WUp9wvj8TGlOxEdrRJ0ydyJ9+H8=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMD5V5sSprk/tFIgy+x1BeKNGhLlFkqfggLpgN7GYOA1rogjxr4d6PXPDk+Lwx3WUp9wvj8TGlOxEdrRJ0ydyJ9+H8ANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFAQAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAgrulAaqUdKeVYM0F+pu6on/h6LBpOyzOKG4xFIKcoFk4ALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181365,
|
||||
"random": "8xljlOhkDlkafEF5vu3e1r3fWvh8AX548wC3hLZ3szQ=",
|
||||
"sessionId": "00uvDYKnFyZFKyf3HlLwWGCOyeHsPFiU5UZ+Fs5pDAU=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMD8xljlOhkDlkafEF5vu3e1r3fWvh8AX548wC3hLZ3szQg00uvDYKnFyZFKyf3HlLwWGCOyeHsPFiU5UZ+Fs5pDAUANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFAQAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAg/9P7140NtKzjyDwBf99mOy1+FjRPAPHTNQ9WxHOKpV4ALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"time": 1617181352,
|
||||
"random": "zja3MLZ8WGSfsQRtPV75+tY6gbK3zKPi1Sy7SBBafg4=",
|
||||
"sessionId": "qPut2yMqXa9zGLII/872SQ3d4Tfqo0uoDb7tpkRfBnA=",
|
||||
"host": "storage.googleapis.com",
|
||||
"cipherSuite": 4867,
|
||||
"full": "AQAB/AMDzja3MLZ8WGSfsQRtPV75+tY6gbK3zKPi1Sy7SBBafg4gqPut2yMqXa9zGLII/872SQ3d4Tfqo0uoDb7tpkRfBnAANBMDEwETAsAswCvAJMAjwArACcypwDDAL8AowCfAFMATzKgAnQCcAD0APAA1AC/ACMASAAoBAAF//wEAAQAAAAAbABkAABZzdG9yYWdlLmdvb2dsZWFwaXMuY29tABcAAAANABgAFgQDCAQEAQUDAgMIBQgFBQEIBgYBAgEABQAFAQAAAAAzdAAAABIAAAAQADAALgJoMgVoMi0xNgVoMi0xNQVoMi0xNAhzcGR5LzMuMQZzcGR5LzMIaHR0cC8xLjEACwACAQAAMwAmACQAHQAgXviLRAqAYJ8xOLdlcsUhldI4Xl0g/s9+y2Qrd8raPEgALQACAQEAKwAJCAMEAwMDAgMBAAoACgAIAB0AFwAYABkAFQChAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package faketls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
mrand "math/rand/v2"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
func SendWelcomePacket(writer io.Writer, secret []byte, clientHello ClientHello) error {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
rec.Type = record.TypeHandshake
|
||||
rec.Version = record.Version12
|
||||
|
||||
generateServerHello(&rec.Payload, clientHello)
|
||||
rec.Dump(buf) //nolint: errcheck
|
||||
rec.Reset()
|
||||
|
||||
rec.Type = record.TypeChangeCipherSpec
|
||||
rec.Version = record.Version12
|
||||
rec.Payload.WriteByte(ChangeCipherValue)
|
||||
|
||||
rec.Dump(buf) //nolint: errcheck
|
||||
rec.Reset()
|
||||
|
||||
rec.Type = record.TypeApplicationData
|
||||
rec.Version = record.Version12
|
||||
|
||||
if _, err := io.CopyN(&rec.Payload, rand.Reader, int64(1024+mrand.IntN(3092))); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rec.Dump(buf) //nolint: errcheck
|
||||
|
||||
packet := buf.Bytes()
|
||||
mac := hmac.New(sha256.New, secret)
|
||||
|
||||
mac.Write(clientHello.Random[:])
|
||||
mac.Write(packet)
|
||||
|
||||
copy(packet[WelcomePacketRandomOffset:], mac.Sum(nil))
|
||||
|
||||
if _, err := writer.Write(packet); err != nil {
|
||||
return err //nolint: wrapcheck
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateServerHello(writer io.Writer, clientHello ClientHello) {
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
|
||||
sliceBuf := [2]byte{}
|
||||
digest := [RandomLen]byte{}
|
||||
|
||||
binary.BigEndian.PutUint16(sliceBuf[:], uint16(record.Version12))
|
||||
bodyBuf.Write(sliceBuf[:])
|
||||
bodyBuf.Write(digest[:])
|
||||
bodyBuf.WriteByte(byte(len(clientHello.SessionID)))
|
||||
bodyBuf.Write(clientHello.SessionID)
|
||||
|
||||
binary.BigEndian.PutUint16(sliceBuf[:], clientHello.CipherSuite)
|
||||
bodyBuf.Write(sliceBuf[:])
|
||||
bodyBuf.Write(serverHelloSuffix)
|
||||
|
||||
scalar := [32]byte{}
|
||||
|
||||
if _, err := rand.Read(scalar[:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
curve, _ := curve25519.X25519(scalar[:], curve25519.Basepoint)
|
||||
bodyBuf.Write(curve)
|
||||
|
||||
header := [4]byte{0, 0, 0, 0}
|
||||
binary.BigEndian.PutUint32(header[:], uint32(bodyBuf.Len()))
|
||||
header[0] = HandshakeTypeServer
|
||||
|
||||
writer.Write(header[:]) //nolint: errcheck
|
||||
bodyBuf.WriteTo(writer) //nolint: errcheck
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package faketls_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/9seconds/mtg/v2/mtglib"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls"
|
||||
"github.com/9seconds/mtg/v2/mtglib/internal/faketls/record"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type WelcomeTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
h *faketls.ClientHello
|
||||
buf *bytes.Buffer
|
||||
secret mtglib.Secret
|
||||
}
|
||||
|
||||
func (suite *WelcomeTestSuite) SetupTest() {
|
||||
suite.h = &faketls.ClientHello{
|
||||
Time: time.Now(),
|
||||
Host: "google.com",
|
||||
CipherSuite: 4867,
|
||||
SessionID: make([]byte, 32),
|
||||
}
|
||||
|
||||
_, err := rand.Read(suite.h.SessionID) //nolint: staticcheck
|
||||
suite.NoError(err)
|
||||
|
||||
_, err = rand.Read(suite.h.Random[:]) //nolint: staticcheck
|
||||
suite.NoError(err)
|
||||
|
||||
suite.buf = &bytes.Buffer{}
|
||||
|
||||
suite.secret = mtglib.GenerateSecret("google.com")
|
||||
}
|
||||
|
||||
func (suite *WelcomeTestSuite) TestOk() {
|
||||
suite.NoError(faketls.SendWelcomePacket(suite.buf, suite.secret.Key[:], *suite.h))
|
||||
|
||||
welcomePacket := []byte{}
|
||||
welcomePacket = append(welcomePacket, suite.buf.Bytes()...)
|
||||
|
||||
rec := record.AcquireRecord()
|
||||
defer record.ReleaseRecord(rec)
|
||||
|
||||
suite.NoError(rec.Read(suite.buf))
|
||||
suite.Equal(record.TypeHandshake, rec.Type)
|
||||
suite.Equal(record.Version12, rec.Version)
|
||||
|
||||
suite.NoError(rec.Read(suite.buf))
|
||||
suite.Equal(record.TypeChangeCipherSpec, rec.Type)
|
||||
suite.Equal(record.Version12, rec.Version)
|
||||
|
||||
suite.NoError(rec.Read(suite.buf))
|
||||
suite.Equal(record.TypeApplicationData, rec.Type)
|
||||
suite.Equal(record.Version12, rec.Version)
|
||||
suite.Empty(suite.buf.Bytes())
|
||||
|
||||
random := make([]byte, 32)
|
||||
copy(random, welcomePacket[11:])
|
||||
|
||||
empty := make([]byte, 32)
|
||||
copy(welcomePacket[11:], empty)
|
||||
|
||||
mac := hmac.New(sha256.New, suite.secret.Key[:])
|
||||
mac.Write(suite.h.Random[:])
|
||||
mac.Write(welcomePacket)
|
||||
|
||||
suite.Equal(random, mac.Sum(nil))
|
||||
}
|
||||
|
||||
func TestWelcome(t *testing.T) {
|
||||
t.Parallel()
|
||||
suite.Run(t, &WelcomeTestSuite{})
|
||||
}
|
||||
Reference in New Issue
Block a user