mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
?!
This commit is contained in:
parent
b458d3cd03
commit
7c9cbef41b
@ -1,5 +1,3 @@
|
|||||||
//go:build with_mtproto
|
|
||||||
|
|
||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -13,7 +11,10 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/mtproto"
|
"github.com/sagernet/sing-box/transport/mtproto"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/replay"
|
"github.com/sagernet/sing/common/replay"
|
||||||
)
|
)
|
||||||
@ -25,7 +26,8 @@ var (
|
|||||||
|
|
||||||
type MTProto struct {
|
type MTProto struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
secret mtproto.Secret
|
userList []string
|
||||||
|
secretList []*mtproto.Secret
|
||||||
replayCache replay.Filter
|
replayCache replay.Filter
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,33 +43,66 @@ func NewMTProto(ctx context.Context, router adapter.Router, logger log.ContextLo
|
|||||||
listenOptions: options.ListenOptions,
|
listenOptions: options.ListenOptions,
|
||||||
},
|
},
|
||||||
replayCache: replay.NewSimple(time.Minute),
|
replayCache: replay.NewSimple(time.Minute),
|
||||||
|
// testDataCenter: options.TestDataCenter,
|
||||||
}
|
}
|
||||||
inbound.connHandler = inbound
|
inbound.connHandler = inbound
|
||||||
var err error
|
err := inbound.UpdateUsers(common.MapIndexed(options.Users, func(index int, user option.MTProtoUser) string {
|
||||||
inbound.secret, err = mtproto.ParseSecret(options.Secret)
|
return user.Name
|
||||||
|
}), common.Map(options.Users, func(user option.MTProtoUser) string {
|
||||||
|
return user.Secret
|
||||||
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbound, nil
|
return inbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MTProto) UpdateUsers(userList []string, secretTextList []string) error {
|
||||||
|
secretList := make([]*mtproto.Secret, len(secretTextList))
|
||||||
|
for i, secretText := range secretTextList {
|
||||||
|
userName := userList[i]
|
||||||
|
if userName == "" {
|
||||||
|
userName = F.ToString(i)
|
||||||
|
}
|
||||||
|
if secretText == "" {
|
||||||
|
return E.New("missing secret for user ", userName)
|
||||||
|
}
|
||||||
|
secret, err := mtproto.ParseSecret(secretText)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "parse user ", userName)
|
||||||
|
}
|
||||||
|
secretList[i] = secret
|
||||||
|
}
|
||||||
|
m.userList = userList
|
||||||
|
m.secretList = secretList
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MTProto) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (m *MTProto) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
fakeTLSConn, err := mtproto.FakeTLSHandshake(ctx, conn, m.secret, m.replayCache)
|
secretIndex, fakeTLSConn, err := mtproto.FakeTLSHandshake(ctx, conn, m.secretList, m.replayCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dc, err := mtproto.Obfs2ClientHandshake(m.secret.Key[:], fakeTLSConn)
|
dataCenter, err := mtproto.Obfs2ClientHandshake(m.secretList[secretIndex].Key[:], fakeTLSConn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !mtproto.AddressPool.IsValidDC(dc) {
|
|
||||||
return E.New("unknown DC: ", dc)
|
|
||||||
}
|
|
||||||
dcAddr := mtproto.AddressPool.GetV4(dc)
|
|
||||||
|
|
||||||
|
userName := m.userList[secretIndex]
|
||||||
|
if userName == "" {
|
||||||
|
userName = F.ToString(secretIndex)
|
||||||
|
}
|
||||||
|
m.logger.InfoContext(ctx, "[", userName, "] inbound connection to Telegram DC ", dataCenter)
|
||||||
metadata.Protocol = "mtproto"
|
metadata.Protocol = "mtproto"
|
||||||
metadata.Destination = dcAddr[0]
|
metadata.Destination = M.Socksaddr{
|
||||||
|
Fqdn: mtproto.DataCenterName(dataCenter) + ".telegram.sing-box.arpa",
|
||||||
|
Port: 443,
|
||||||
|
}
|
||||||
|
serverAddress := mtproto.ProductionDataCenterAddress[dataCenter]
|
||||||
|
if len(serverAddress) == 0 {
|
||||||
|
m.logger.Debug("unknown data center: ", dataCenter)
|
||||||
|
}
|
||||||
|
metadata.DestinationAddresses = serverAddress
|
||||||
return m.router.RouteConnection(ctx, fakeTLSConn, metadata)
|
return m.router.RouteConnection(ctx, fakeTLSConn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
//go:build !with_mtproto
|
|
||||||
|
|
||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMTProto(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MTProtoInboundOptions) (adapter.Inbound, error) {
|
|
||||||
return nil, E.New(`MTProto is not included in this build, rebuild with -tags with_mtproto`)
|
|
||||||
}
|
|
@ -2,5 +2,10 @@ package option
|
|||||||
|
|
||||||
type MTProtoInboundOptions struct {
|
type MTProtoInboundOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
|
Users []MTProtoUser `json:"users,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MTProtoUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
Secret string `json:"secret"`
|
Secret string `json:"secret"`
|
||||||
}
|
}
|
||||||
|
@ -1,115 +1,63 @@
|
|||||||
package mtproto
|
package mtproto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"net/netip"
|
||||||
|
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var ProductionDataCenterAddress = map[int][]netip.Addr{
|
||||||
// DefaultDC defines a number of the default DC to use. This value used
|
1: {
|
||||||
// only if a value from obfuscated2 handshake frame is 0 (default).
|
M.ParseAddr("149.154.175.50"),
|
||||||
DefaultDC = 2
|
M.ParseAddr("2001:b28:f23d:f001::a"),
|
||||||
)
|
},
|
||||||
|
2: {
|
||||||
|
M.ParseAddr("149.154.167.51"),
|
||||||
|
M.ParseAddr("2001:67c:04e8:f002::a"),
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
M.ParseAddr("149.154.175.100"),
|
||||||
|
M.ParseAddr("2001:b28:f23d:f003::a"),
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
M.ParseAddr("149.154.167.91"),
|
||||||
|
M.ParseAddr("2001:67c:04e8:f004::a"),
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
M.ParseAddr("149.154.171.5"),
|
||||||
|
M.ParseAddr("2001:b28:f23f:f005::a"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
|
var TestDataCenterAddress = map[int][]netip.Addr{
|
||||||
var (
|
1: {
|
||||||
productionV4Addresses = [][]M.Socksaddr{
|
M.ParseAddr("149.154.175.10"),
|
||||||
{ // dc1
|
M.ParseAddr("2001:b28:f23d:f001::e"),
|
||||||
M.ParseSocksaddr("149.154.175.50:443"),
|
|
||||||
},
|
},
|
||||||
{ // dc2
|
2: {
|
||||||
M.ParseSocksaddr("149.154.167.51:443"),
|
M.ParseAddr("149.154.167.40"),
|
||||||
M.ParseSocksaddr("95.161.76.100:443"),
|
M.ParseAddr("2001:67c:04e8:f002::e"),
|
||||||
},
|
},
|
||||||
{ // dc3
|
3: {
|
||||||
M.ParseSocksaddr("149.154.175.100:443"),
|
M.ParseAddr("149.154.175.117"),
|
||||||
},
|
M.ParseAddr("2001:b28:f23d:f003::e"),
|
||||||
{ // dc4
|
|
||||||
M.ParseSocksaddr("149.154.167.91:443"),
|
|
||||||
},
|
|
||||||
{ // dc5
|
|
||||||
M.ParseSocksaddr("149.154.171.5:443"),
|
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func DataCenterName(dataCenter int) string {
|
||||||
|
switch dataCenter {
|
||||||
|
case 1:
|
||||||
|
return "pluto"
|
||||||
|
case 2:
|
||||||
|
return "venus"
|
||||||
|
case 3:
|
||||||
|
return "aurora"
|
||||||
|
case 4:
|
||||||
|
return "vesta"
|
||||||
|
case 5:
|
||||||
|
return "flora"
|
||||||
|
default:
|
||||||
|
return F.ToString(dataCenter)
|
||||||
}
|
}
|
||||||
productionV6Addresses = [][]M.Socksaddr{
|
|
||||||
{ // dc1
|
|
||||||
M.ParseSocksaddr("[2001:b28:f23d:f001::a]:443"),
|
|
||||||
},
|
|
||||||
{ // dc2
|
|
||||||
M.ParseSocksaddr("[2001:67c:04e8:f002::a]:443"),
|
|
||||||
},
|
|
||||||
{ // dc3
|
|
||||||
M.ParseSocksaddr("[2001:b28:f23d:f003::a]:443"),
|
|
||||||
},
|
|
||||||
{ // dc4
|
|
||||||
M.ParseSocksaddr("[2001:67c:04e8:f004::a]:443"),
|
|
||||||
},
|
|
||||||
{ // dc5
|
|
||||||
M.ParseSocksaddr("[2001:b28:f23f:f005::a]:443"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/*testV4Addresses = [][]M.Socksaddr{
|
|
||||||
{ // dc1
|
|
||||||
M.ParseSocksaddr("149.154.175.10:443"),
|
|
||||||
},
|
|
||||||
{ // dc2
|
|
||||||
M.ParseSocksaddr("149.154.167.40:443"),
|
|
||||||
},
|
|
||||||
{ // dc3
|
|
||||||
M.ParseSocksaddr("149.154.175.117:443"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testV6Addresses = [][]M.Socksaddr{
|
|
||||||
{ // dc1
|
|
||||||
M.ParseSocksaddr("[2001:b28:f23d:f001::e]:443"),
|
|
||||||
},
|
|
||||||
{ // dc2
|
|
||||||
M.ParseSocksaddr("[2001:67c:04e8:f002::e]:443"),
|
|
||||||
},
|
|
||||||
{ // dc3
|
|
||||||
M.ParseSocksaddr("[2001:b28:f23d:f003::e]:443"),
|
|
||||||
},
|
|
||||||
}*/
|
|
||||||
)
|
|
||||||
|
|
||||||
type addressPool struct {
|
|
||||||
v4 [][]M.Socksaddr
|
|
||||||
v6 [][]M.Socksaddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var AddressPool = addressPool{productionV4Addresses, productionV6Addresses}
|
|
||||||
|
|
||||||
func (a addressPool) IsValidDC(dc int) bool {
|
|
||||||
return dc > 0 && dc <= len(a.v4) && dc <= len(a.v6)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a addressPool) getRandomDC() int {
|
|
||||||
return 1 + rand.Intn(len(a.v4))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a addressPool) GetV4(dc int) []M.Socksaddr {
|
|
||||||
return a.get(a.v4, dc-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a addressPool) GetV6(dc int) []M.Socksaddr {
|
|
||||||
return a.get(a.v6, dc-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a addressPool) get(addresses [][]M.Socksaddr, dc int) []M.Socksaddr {
|
|
||||||
if dc < 0 || dc >= len(addresses) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rv := make([]M.Socksaddr, len(addresses[dc]))
|
|
||||||
copy(rv, addresses[dc])
|
|
||||||
|
|
||||||
if len(rv) > 1 {
|
|
||||||
rand.Shuffle(len(rv), func(i, j int) {
|
|
||||||
rv[i], rv[j] = rv[j], rv[i]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv
|
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func FakeTLSHandshake(ctx context.Context, conn net.Conn, secret Secret, replay replay.Filter) (*FakeTLSConn, error) {
|
func FakeTLSHandshake(ctx context.Context, conn net.Conn, secrets []*Secret, replay replay.Filter) (secretIndex int, fakeTLSConn *FakeTLSConn, err error) {
|
||||||
_record := buf.StackNew()
|
_record := buf.StackNew()
|
||||||
defer common.KeepAlive(_record)
|
defer common.KeepAlive(_record)
|
||||||
record := common.Dup(_record)
|
record := common.Dup(_record)
|
||||||
@ -78,44 +78,59 @@ func FakeTLSHandshake(ctx context.Context, conn net.Conn, secret Secret, replay
|
|||||||
recordHeaderLen+=1 // type
|
recordHeaderLen+=1 // type
|
||||||
recordHeaderLen+=2 // version
|
recordHeaderLen+=2 // version
|
||||||
recordHeaderLen+=2 // payload length*/
|
recordHeaderLen+=2 // payload length*/
|
||||||
_, err := record.ReadFullFrom(conn, 5)
|
_, err = record.ReadFullFrom(conn, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read FakeTLS record")
|
err = E.Cause(err, "read FakeTLS record")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
recordType := record.Byte(0)
|
recordType := record.Byte(0)
|
||||||
switch recordType {
|
switch recordType {
|
||||||
case TypeChangeCipherSpec, TypeHandshake, TypeApplicationData:
|
case TypeChangeCipherSpec, TypeHandshake, TypeApplicationData:
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown record type: ", recordType)
|
err = E.New("unknown record type: ", recordType)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
version := binary.BigEndian.Uint16(record.Range(1, 3))
|
version := binary.BigEndian.Uint16(record.Range(1, 3))
|
||||||
switch version {
|
switch version {
|
||||||
case Version10, Version11, Version12, Version13:
|
case Version10, Version11, Version12, Version13:
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown tls version: ", version)
|
err = E.New("unknown TLS version: ", version)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
length := int(binary.BigEndian.Uint16(record.Range(3, 5)))
|
length := int(binary.BigEndian.Uint16(record.Range(3, 5)))
|
||||||
record.Reset()
|
record.Reset()
|
||||||
_, err = record.ReadFullFrom(conn, length)
|
_, err = record.ReadFullFrom(conn, length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read FakeTLS record")
|
err = E.Cause(err, "read FakeTLS record")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hello, err := parseClientHello(secret, record)
|
var clientHello *ClientHello
|
||||||
|
var foundSecret *Secret
|
||||||
|
for i, secret := range secrets {
|
||||||
|
clientHello, err = parseClientHello(secret, record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
continue
|
||||||
}
|
}
|
||||||
|
err = clientHello.Valid(secret.Host, time.Minute)
|
||||||
err = hello.Valid(secret.Host, time.Minute)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
continue
|
||||||
|
}
|
||||||
|
secretIndex = i
|
||||||
|
foundSecret = secret
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if foundSecret == nil {
|
||||||
|
err = E.New("bad request")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !replay.Check(hello.SessionID) {
|
if !replay.Check(clientHello.SessionID) {
|
||||||
return nil, E.New("replay attack detected: ", hex.EncodeToString(hello.SessionID))
|
err = E.New("replay attack detected: ", hex.EncodeToString(clientHello.SessionID))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_serverHello := buf.StackNew()
|
_serverHello := buf.StackNew()
|
||||||
@ -123,11 +138,11 @@ func FakeTLSHandshake(ctx context.Context, conn net.Conn, secret Secret, replay
|
|||||||
serverHello := common.Dup(_serverHello)
|
serverHello := common.Dup(_serverHello)
|
||||||
defer serverHello.Release()
|
defer serverHello.Release()
|
||||||
|
|
||||||
generateServerHello(serverHello, hello)
|
generateServerHello(serverHello, clientHello)
|
||||||
common.Must1(serverHello.Write(serverChangeCipherSpec))
|
common.Must1(serverHello.Write(serverChangeCipherSpec))
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, secret.Key[:])
|
mac := hmac.New(sha256.New, foundSecret.Key[:])
|
||||||
mac.Write(hello.Random[:])
|
mac.Write(clientHello.Random[:])
|
||||||
|
|
||||||
appDataHeader := serverHello.Extend(5)
|
appDataHeader := serverHello.Extend(5)
|
||||||
appDataRandomLen := 1024 + mrand.Intn(3092)
|
appDataRandomLen := 1024 + mrand.Intn(3092)
|
||||||
@ -142,9 +157,10 @@ func FakeTLSHandshake(ctx context.Context, conn net.Conn, secret Secret, replay
|
|||||||
|
|
||||||
_, err = serverHello.WriteTo(conn)
|
_, err = serverHello.WriteTo(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return
|
||||||
}
|
}
|
||||||
return &FakeTLSConn{Conn: conn}, nil
|
fakeTLSConn = &FakeTLSConn{Conn: conn}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientHello struct {
|
type ClientHello struct {
|
||||||
@ -175,7 +191,7 @@ func (c *ClientHello) Valid(hostname string, tolerateTimeSkewness time.Duration)
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseClientHello(secret Secret, handshake *buf.Buffer) (*ClientHello, error) {
|
func parseClientHello(secret *Secret, handshake *buf.Buffer) (*ClientHello, error) {
|
||||||
l := handshake.Len()
|
l := handshake.Len()
|
||||||
if l < 6 { // minimum client hello length
|
if l < 6 { // minimum client hello length
|
||||||
return nil, E.New("client hello too short: ", l)
|
return nil, E.New("client hello too short: ", l)
|
||||||
|
@ -57,7 +57,7 @@ func (f *clientHandshakeFrame) dc() int {
|
|||||||
case idx < 0:
|
case idx < 0:
|
||||||
return -int(idx)
|
return -int(idx)
|
||||||
default:
|
default:
|
||||||
return DefaultDC
|
return 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,26 +1,17 @@
|
|||||||
package mtproto
|
package mtproto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// kanged from https://github.com/9seconds/mtg/blob/master/mtglib/secret.go
|
// mod from https://github.com/9seconds/mtg/blob/master/mtglib/secret.go
|
||||||
|
|
||||||
const (
|
const (
|
||||||
secretFakeTLSFirstByte byte = 0xEE
|
secretFakeTLSFirstByte byte = 0xEE
|
||||||
|
secretKeyLength = 16
|
||||||
SecretKeyLength = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
secretEmptyKey [SecretKeyLength]byte
|
|
||||||
|
|
||||||
ErrSecretEmpty = E.New("mtproto: secret is empty")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Secret is a data structure that presents a secret.
|
// Secret is a data structure that presents a secret.
|
||||||
@ -51,88 +42,35 @@ var (
|
|||||||
// forms into bytes, you'll get the same byte array. Telegram clients nowadays
|
// forms into bytes, you'll get the same byte array. Telegram clients nowadays
|
||||||
// accept all forms.
|
// accept all forms.
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
// Key is a set of bytes used for traffic authentication.
|
Key [secretKeyLength]byte
|
||||||
Key [SecretKeyLength]byte
|
|
||||||
|
|
||||||
// Host is a domain fronting hostname.
|
|
||||||
Host string
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Secret) Set(text string) error {
|
func (s *Secret) String() string {
|
||||||
if text == "" {
|
|
||||||
return ErrSecretEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := hex.DecodeString(text)
|
|
||||||
if err != nil {
|
|
||||||
decoded, err = base64.RawURLEncoding.DecodeString(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return E.New("incorrect secret format: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
l := len(decoded)
|
|
||||||
if l < 2 { //nolint: gomnd // we need at least 1 byte here
|
|
||||||
return E.New("secret is truncated, length=", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
if decoded[0] != secretFakeTLSFirstByte {
|
|
||||||
return E.New("incorrect first byte of secret: ", decoded[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if l < 1+SecretKeyLength { // 1 for FakeTLS first byte
|
|
||||||
return E.New("secret has incorrect length ", len(decoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(s.Key[:], decoded[1:SecretKeyLength+1])
|
|
||||||
s.Host = string(decoded[1+SecretKeyLength:])
|
|
||||||
|
|
||||||
if s.Host == "" {
|
|
||||||
return E.New("hostname cannot be empty: ", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid checks if this secret is valid and can be used in proxy.
|
|
||||||
func (s Secret) Valid() bool {
|
|
||||||
return s.Key != secretEmptyKey && s.Host != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is to support fmt.Stringer interface.
|
|
||||||
func (s Secret) String() string {
|
|
||||||
return s.Base64()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base64 returns a base64-encoded form of this secret.
|
|
||||||
func (s Secret) Base64() string {
|
|
||||||
return base64.RawURLEncoding.EncodeToString(s.makeBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hex returns a hex-encoded form of this secret (ee-secret).
|
|
||||||
func (s Secret) Hex() string {
|
|
||||||
return hex.EncodeToString(s.makeBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Secret) makeBytes() []byte {
|
|
||||||
data := append([]byte{secretFakeTLSFirstByte}, s.Key[:]...)
|
data := append([]byte{secretFakeTLSFirstByte}, s.Key[:]...)
|
||||||
data = append(data, s.Host...)
|
data = append(data, s.Host...)
|
||||||
|
return hex.EncodeToString(data)
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSecret makes a new secret with a given hostname.
|
func ParseSecret(plainText string) (*Secret, error) {
|
||||||
func GenerateSecret(hostname string) Secret {
|
decoded, err := hex.DecodeString(plainText)
|
||||||
s := Secret{Host: hostname}
|
if err != nil {
|
||||||
common.Must1(rand.Read(s.Key[:]))
|
decoded, err = base64.RawURLEncoding.DecodeString(plainText)
|
||||||
|
}
|
||||||
return s
|
if err != nil {
|
||||||
}
|
return nil, E.Cause(err, "bad secret format")
|
||||||
|
}
|
||||||
// ParseSecret parses a secret (both hex and base64 forms).
|
if len(decoded) < 2 {
|
||||||
func ParseSecret(secret string) (Secret, error) {
|
return nil, E.New("secret is truncated, length=", len(decoded))
|
||||||
s := Secret{}
|
}
|
||||||
|
if decoded[0] != secretFakeTLSFirstByte || len(decoded) < 1+secretKeyLength {
|
||||||
return s, s.Set(secret)
|
return nil, E.New("bad FakeTLS secret")
|
||||||
|
}
|
||||||
|
var secret Secret
|
||||||
|
copy(secret.Key[:], decoded[1:secretKeyLength+1])
|
||||||
|
secret.Host = string(decoded[1+secretKeyLength:])
|
||||||
|
if secret.Host == "" {
|
||||||
|
return nil, E.New("bad FakeTLS secret: empty server host")
|
||||||
|
}
|
||||||
|
return &secret, nil
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user