Remove timeattack module

This commit is contained in:
9seconds
2021-04-08 14:58:14 +03:00
parent 0652f322fa
commit e2073f0585
13 changed files with 108 additions and 247 deletions
+8 -13
View File
@@ -48,6 +48,14 @@ prefer-ip = "prefer-ipv6"
# access.
domain-fronting-port = 443
# FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if
# we need to proceed with connection or not.
#
# Sometimes time can be skewed so we accept all messages within a
# time range of this parameter.
tolerate-time-skewness = "5s"
# network defines different network-related settings
[network]
# please be aware that mtg needs to do some external requests. For
@@ -111,19 +119,6 @@ tcp = "5s"
http = "10s"
idle = "1m"
# FakeTLS can compare timestamps to prevent probes. Each message has
# encrypted timestamp. So, mtg can compare this timestamp and decide if
# we need to proceed with connection or not.
#
# Please ensure that you have some ntp active on this host. Otherwise,
# you can endup with badly performing proxy.
[defense.time]
# You can enable/disable that. A good idea is always enable.
enabled = true
# Time can be skewed by many reasons. So, this is a time interval
# when message is cosidered as a good one.
allow-skewness = "5s"
# Some countries do active probing on Telegram connections. This technique
# allows to protect from such effort.
#
+5 -18
View File
@@ -12,7 +12,6 @@ import (
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/stats"
"github.com/9seconds/mtg/v2/timeattack"
"github.com/rs/zerolog"
)
@@ -41,12 +40,11 @@ func (c *Proxy) Execute() error {
ctx := utils.RootContext()
opts := mtglib.ProxyOpts{
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
Network: c.Network,
AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
TimeAttackDetector: timeattack.NewNoop(),
EventStream: events.NewNoopStream(),
Logger: logger.NewZeroLogger(zerolog.New(os.Stdout).With().Timestamp().Logger()),
Network: c.Network,
AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
EventStream: events.NewNoopStream(),
Secret: c.Config.Secret,
BufferSize: c.Config.TCPBuffer.Value(mtglib.DefaultBufferSize),
@@ -58,7 +56,6 @@ func (c *Proxy) Execute() error {
opts.Logger.BindStr("configuration", c.Config.String()).Debug("configuration")
c.setupAntiReplayCache(&opts)
c.setupTimeAttackDetector(&opts)
if err := c.setupIPBlocklist(&opts); err != nil {
return fmt.Errorf("cannot setup ipblocklist: %w", err)
@@ -98,16 +95,6 @@ func (c *Proxy) setupAntiReplayCache(opts *mtglib.ProxyOpts) {
)
}
func (c *Proxy) setupTimeAttackDetector(opts *mtglib.ProxyOpts) {
if !c.Config.Defense.Time.Enabled {
return
}
opts.TimeAttackDetector = timeattack.NewDetector(
c.Config.Defense.Time.AllowSkewness.Value(timeattack.DefaultDuration),
)
}
func (c *Proxy) setupIPBlocklist(opts *mtglib.ProxyOpts) error {
if !c.Config.Defense.Blocklist.Enabled {
return nil
+18 -24
View File
@@ -10,18 +10,15 @@ import (
)
type Config struct {
Debug bool `json:"debug"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bind-to"`
TCPBuffer TypeBytes `json:"tcp-buffer"`
PreferIP TypePreferIP `json:"prefer-ip"`
DomainFrontingPort TypePort `json:"domain-fronting-port"`
Concurrency uint `json:"concurrency"`
Defense struct {
Time struct {
Enabled bool `json:"enabled"`
AllowSkewness TypeDuration `json:"allow-skewness"`
} `json:"time"`
Debug bool `json:"debug"`
Secret mtglib.Secret `json:"secret"`
BindTo TypeHostPort `json:"bind-to"`
TCPBuffer TypeBytes `json:"tcp-buffer"`
PreferIP TypePreferIP `json:"prefer-ip"`
DomainFrontingPort TypePort `json:"domain-fronting-port"`
TolerateTimeSkewness TypeDuration `json:"tolerate-time-skewness"`
Concurrency uint `json:"concurrency"`
Defense struct {
AntiReplay struct {
Enabled bool `json:"enabled"`
MaxSize TypeBytes `json:"max-size"`
@@ -85,18 +82,15 @@ func (c *Config) String() string {
}
type configRaw struct {
Debug bool `toml:"debug" json:"debug,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bind-to"`
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
Defense struct {
Time struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
AllowSkewness string `toml:"allow-skewness" json:"allow-skewness,omitempty"`
} `toml:"time" json:"time,omitempty"`
Debug bool `toml:"debug" json:"debug,omitempty"`
Secret string `toml:"secret" json:"secret"`
BindTo string `toml:"bind-to" json:"bind-to"`
TCPBuffer string `toml:"tcp-buffer" json:"tcp-buffer,omitempty"`
PreferIP string `toml:"prefer-ip" json:"prefer-ip,omitempty"`
DomainFrontingPort uint `toml:"domain-fronting-port" json:"domain-fronting-port,omitempty"`
TolerateTimeSkewness string `toml:"tolerate-time-skewness" json:"tolerate-time-skewness,omitempty"`
Concurrency uint `toml:"concurrency" json:"concurrency,omitempty"`
Defense struct {
AntiReplay struct {
Enabled bool `toml:"enabled" json:"enabled,omitempty"`
MaxSize string `toml:"max-size" json:"max-size,omitempty"`
+4 -16
View File
@@ -42,10 +42,6 @@ var (
// create a proxy but anti replay cache value is undefined.
ErrAntiReplayCacheIsNotDefined = errors.New("anti-replay cache is not defined")
// ErrTimeAttackDetectorIsNotDefined is returned if you are trying to
// create a proxy but time attack detector is not defined.
ErrTimeAttackDetectorIsNotDefined = errors.New("time attack detector is not defined")
// ErrIPBlocklistIsNotDefined is returned if you are trying to
// create a proxy but ip blocklist instance is not defined.
ErrIPBlocklistIsNotDefined = errors.New("ip blocklist is not defined")
@@ -75,6 +71,10 @@ const (
// in case of idling.
DefaultIdleTimeout = time.Minute
// DefaultTolerateTimeSkewness is a default timeout for time
// skewness on a faketls timeout verification.
DefaultTolerateTimeSkewness = 3 * time.Second
// DefaultPreferIP is a default value for Telegram IP connectivity
// preference.
DefaultPreferIP = "prefer-ipv6"
@@ -206,18 +206,6 @@ type EventStream interface {
Send(context.Context, Event)
}
// TimeAttackDetector is an abstraction that checks a time, taken from
// the faketls client hello message. This timestamp is encoded into
// client-generated random bytes and can be extracted after some client
// hello verification.
//
// This is mostly to prevent replay attacks.
type TimeAttackDetector interface {
// Valid returns an error if timestamp is invalid or should not be
// accepted.
Valid(time.Time) error
}
// Logger defines an interface of the logger used by mtglib.
//
// Each logger has a name. It is possible to stack names to organize
+20
View File
@@ -19,6 +19,26 @@ type ClientHello struct {
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{}
+30 -34
View File
@@ -23,19 +23,19 @@ type Proxy struct {
ctxCancel context.CancelFunc
streamWaitGroup sync.WaitGroup
idleTimeout time.Duration
bufferSize int
domainFrontingPort int
workerPool *ants.PoolWithFunc
telegram *telegram.Telegram
idleTimeout time.Duration
tolerateTimeSkewness time.Duration
bufferSize int
domainFrontingPort int
workerPool *ants.PoolWithFunc
telegram *telegram.Telegram
secret Secret
network Network
antiReplayCache AntiReplayCache
timeAttackDetector TimeAttackDetector
ipBlocklist IPBlocklist
eventStream EventStream
logger Logger
secret Secret
network Network
antiReplayCache AntiReplayCache
ipBlocklist IPBlocklist
eventStream EventStream
logger Logger
}
// DomainFrontingAddress returns a host:port pair for a fronting domain.
@@ -159,15 +159,11 @@ func (p *Proxy) doFakeTLSHandshake(ctx *streamContext) bool {
return false
}
if hello.Host != "" && hello.Host != p.secret.Host {
p.logger.BindStr("hostname", hello.Host).Info("incorrect domain was found in SNI")
p.doDomainFronting(ctx, rewind)
return false
}
if err := p.timeAttackDetector.Valid(hello.Time); err != nil {
p.logger.InfoError("invalid faketls time", err)
if err := hello.Valid(p.secret.Host, p.tolerateTimeSkewness); err != nil {
p.logger.
BindStr("hostname", hello.Host).
BindStr("hello-time", hello.Time.String()).
InfoError("invalid faketls client hello", err)
p.doDomainFronting(ctx, rewind)
return false
@@ -281,19 +277,19 @@ func NewProxy(opts ProxyOpts) (*Proxy, error) {
ctx, cancel := context.WithCancel(context.Background())
proxy := &Proxy{
ctx: ctx,
ctxCancel: cancel,
secret: opts.Secret,
network: opts.Network,
antiReplayCache: opts.AntiReplayCache,
timeAttackDetector: opts.TimeAttackDetector,
ipBlocklist: opts.IPBlocklist,
eventStream: opts.EventStream,
logger: opts.getLogger("proxy"),
domainFrontingPort: opts.getDomainFrontingPort(),
idleTimeout: opts.getIdleTimeout(),
bufferSize: opts.getBufferSize(),
telegram: tg,
ctx: ctx,
ctxCancel: cancel,
secret: opts.Secret,
network: opts.Network,
antiReplayCache: opts.AntiReplayCache,
ipBlocklist: opts.IPBlocklist,
eventStream: opts.EventStream,
logger: opts.getLogger("proxy"),
domainFrontingPort: opts.getDomainFrontingPort(),
tolerateTimeSkewness: opts.getTolerateTimeSkewness(),
idleTimeout: opts.getIdleTimeout(),
bufferSize: opts.getBufferSize(),
telegram: tg,
}
pool, err := ants.NewPoolWithFunc(opts.getConcurrency(),
+17 -7
View File
@@ -23,11 +23,6 @@ type ProxyOpts struct {
// This is a mandatory setting.
AntiReplayCache AntiReplayCache
// TimeAttackDetector defines an instance of timeattack detector.
//
// This is a mandatory setting.
TimeAttackDetector TimeAttackDetector
// IPBlocklist defines an instance of IP blocklist.
//
// This is a mandatory setting.
@@ -80,6 +75,15 @@ type ProxyOpts struct {
// This is an optional setting.
IdleTimeout time.Duration
// TolerateTimeSkewness is a time boundary that defines a time
// range where faketls timestamp is acceptable.
//
// This means that if if you got a timestamp X, now is Y, then
// if |X-Y| < TolerateTimeSkewness, then you accept a packet.
//
// This is an optional setting.
TolerateTimeSkewness time.Duration
// PreferIP defines an IP connectivity preference. Valid values are:
// 'prefer-ipv4', 'prefer-ipv6', 'only-ipv4', 'only-ipv6'.
//
@@ -97,8 +101,6 @@ func (p ProxyOpts) valid() error {
return ErrIPBlocklistIsNotDefined
case p.EventStream == nil:
return ErrEventStreamIsNotDefined
case p.TimeAttackDetector == nil:
return ErrTimeAttackDetectorIsNotDefined
case p.Logger == nil:
return ErrLoggerIsNotDefined
case !p.Secret.Valid():
@@ -140,6 +142,14 @@ func (p ProxyOpts) getIdleTimeout() time.Duration {
return p.IdleTimeout
}
func (p ProxyOpts) getTolerateTimeSkewness() time.Duration {
if p.TolerateTimeSkewness == 0 {
return DefaultTolerateTimeSkewness
}
return p.TolerateTimeSkewness
}
func (p ProxyOpts) getPreferIP() string {
if p.PreferIP == "" {
return DefaultPreferIP
+6 -16
View File
@@ -16,7 +16,6 @@ import (
"github.com/9seconds/mtg/v2/logger"
"github.com/9seconds/mtg/v2/mtglib"
"github.com/9seconds/mtg/v2/network"
"github.com/9seconds/mtg/v2/timeattack"
"github.com/stretchr/testify/suite"
)
@@ -46,13 +45,12 @@ func (suite *ProxyTestSuite) SetupSuite() {
suite.NoError(err)
suite.opts = &mtglib.ProxyOpts{
Secret: mtglib.GenerateSecret("httpbin.org"),
Network: ntw,
AntiReplayCache: antireplay.NewNoop(),
TimeAttackDetector: timeattack.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
EventStream: events.NewNoopStream(),
Logger: logger.NewNoopLogger(),
Secret: mtglib.GenerateSecret("httpbin.org"),
Network: ntw,
AntiReplayCache: antireplay.NewNoop(),
IPBlocklist: ipblocklist.NewNoop(),
EventStream: events.NewNoopStream(),
Logger: logger.NewNoopLogger(),
}
proxy, err := mtglib.NewProxy(*suite.opts)
@@ -118,14 +116,6 @@ func (suite *ProxyTestSuite) TestCannotInitNoEventStream() {
suite.Error(err)
}
func (suite *ProxyTestSuite) TestCannotInitNoTimeAttackDetector() {
opts := *suite.opts
opts.TimeAttackDetector = nil
_, err := mtglib.NewProxy(opts)
suite.Error(err)
}
func (suite *ProxyTestSuite) TestCannotInitNoLogger() {
opts := *suite.opts
opts.Logger = nil
-39
View File
@@ -1,39 +0,0 @@
package timeattack
import (
"fmt"
"time"
"github.com/9seconds/mtg/v2/mtglib"
)
type detector struct {
time.Duration
}
func (d detector) Valid(then time.Time) error {
now := time.Now()
diff := now.Sub(then)
if diff < 0 {
diff = -diff
}
if diff > d.Duration {
return fmt.Errorf("time is invalid. now=%d, then=%d, diff=%v",
now.Unix(),
then.Unix(),
diff)
}
return nil
}
// NewDetector returns a new TimeAttackDetector which validates that
// timestamp belongs to intervar [X-duration, X+duration], so a small
// timeshift is acceptable.
func NewDetector(duration time.Duration) mtglib.TimeAttackDetector {
return detector{
Duration: duration,
}
}
-28
View File
@@ -1,28 +0,0 @@
package timeattack_test
import (
"testing"
"time"
"github.com/9seconds/mtg/v2/timeattack"
"github.com/stretchr/testify/suite"
)
type DetectorTestSuite struct {
suite.Suite
}
func (suite *DetectorTestSuite) TestOp() {
d := timeattack.NewDetector(time.Second)
suite.NoError(d.Valid(time.Now()))
suite.NoError(d.Valid(time.Now().Add(100 * time.Millisecond)))
suite.NoError(d.Valid(time.Now().Add(-100 * time.Millisecond)))
suite.Error(d.Valid(time.Now().Add(time.Hour)))
suite.Error(d.Valid(time.Now().Add(-time.Hour)))
}
func TestDetector(t *testing.T) {
t.Parallel()
suite.Run(t, &DetectorTestSuite{})
}
-10
View File
@@ -1,10 +0,0 @@
// TimeAttack has implementation of mtglib.TimeAttackDetector.
package timeattack
import "time"
// DefaultDuration is a default duration when timestamps are acceptable.
//
// It means that all timestamps which are X-DefaultDuration <= X <=
// X+DefaultDuration are fine.
const DefaultDuration = 5 * time.Second
-16
View File
@@ -1,16 +0,0 @@
package timeattack
import (
"time"
"github.com/9seconds/mtg/v2/mtglib"
)
type noop struct{}
func (n noop) Valid(_ time.Time) error { return nil }
// NewNoop returns TimeAttackDetector which accepts all timestamps.
func NewNoop() mtglib.TimeAttackDetector {
return noop{}
}
-26
View File
@@ -1,26 +0,0 @@
package timeattack_test
import (
"testing"
"time"
"github.com/9seconds/mtg/v2/timeattack"
"github.com/stretchr/testify/suite"
)
type NoopTestSuite struct {
suite.Suite
}
func (suite *NoopTestSuite) TestOp() {
d := timeattack.NewNoop()
suite.NoError(d.Valid(time.Now()))
suite.NoError(d.Valid(time.Now().Add(time.Hour)))
suite.NoError(d.Valid(time.Now().Add(-time.Hour)))
}
func TestNoop(t *testing.T) {
t.Parallel()
suite.Run(t, &NoopTestSuite{})
}