Skip to content

Commit

Permalink
Add ShadowTLS protocol v3
Browse files Browse the repository at this point in the history
  • Loading branch information
nekohasekai authored Feb 18, 2023
1 parent 1610bdc commit 21cb227
Show file tree
Hide file tree
Showing 53 changed files with 23,127 additions and 229 deletions.
2 changes: 2 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ run:
- transport/simple-obfs
- transport/clashssr
- transport/cloudflaretls
- transport/shadowtls/tls
- transport/shadowtls/tls_go119

linters-settings:
gci:
Expand Down
7 changes: 4 additions & 3 deletions common/tls/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
)

type (
STDConfig = tls.Config
STDConn = tls.Conn
STDConfig = tls.Config
STDConn = tls.Conn
ConnectionState = tls.ConnectionState
)

type Config interface {
Expand All @@ -33,7 +34,7 @@ type ServerConfig interface {
type Conn interface {
net.Conn
HandshakeContext(ctx context.Context) error
ConnectionState() tls.ConnectionState
ConnectionState() ConnectionState
}

func ParseTLSVersion(version string) (uint16, error) {
Expand Down
5 changes: 3 additions & 2 deletions docs/configuration/outbound/shadowtls.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

"server": "127.0.0.1",
"server_port": 1080,
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"tls": {},

Expand Down Expand Up @@ -37,12 +37,13 @@ ShadowTLS protocol version.
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |

#### password

Set password.

Only available in the ShadowTLS v2 protocol.
Only available in the ShadowTLS v2/v3 protocol.

#### tls

Expand Down
5 changes: 3 additions & 2 deletions docs/configuration/outbound/shadowtls.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

"server": "127.0.0.1",
"server_port": 1080,
"version": 2,
"version": 3,
"password": "fuck me till the daylight",
"tls": {},

Expand Down Expand Up @@ -37,12 +37,13 @@ ShadowTLS 协议版本。
|---------------|-----------------------------------------------------------------------------------------|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |

#### password

设置密码。

仅在 ShadowTLS v2 协议中可用。
仅在 ShadowTLS v2/v3 协议中可用。

#### tls

Expand Down
104 changes: 100 additions & 4 deletions inbound/shadowtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package inbound
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"encoding/hex"
"io"
"net"
"os"
Expand All @@ -27,7 +30,7 @@ type ShadowTLS struct {
myInboundAdapter
handshakeDialer N.Dialer
handshakeAddr M.Socksaddr
v2 bool
version int
password string
fallbackAfter int
}
Expand All @@ -47,17 +50,18 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
handshakeAddr: options.Handshake.ServerOptions.Build(),
password: options.Password,
}
inbound.version = options.Version
switch options.Version {
case 0:
fallthrough
case 1:
case 2:
inbound.v2 = true
if options.FallbackAfter == nil {
inbound.fallbackAfter = 2
} else {
inbound.fallbackAfter = *options.FallbackAfter
}
case 3:
default:
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
}
Expand All @@ -70,7 +74,8 @@ func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata a
if err != nil {
return err
}
if !s.v2 {
switch s.version {
case 1:
var handshake task.Group
handshake.Append("client handshake", func(ctx context.Context) error {
return s.copyUntilHandshakeFinished(handshakeConn, conn)
Expand All @@ -87,7 +92,7 @@ func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata a
return err
}
return s.newConnection(ctx, conn, metadata)
} else {
case 2:
hashConn := shadowtls.NewHashWriteConn(conn, s.password)
go bufio.Copy(hashConn, handshakeConn)
var request *buf.Buffer
Expand All @@ -102,6 +107,97 @@ func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata a
} else {
return err
}
default:
fallthrough
case 3:
var clientHelloFrame *buf.Buffer
clientHelloFrame, err = shadowtls.ExtractFrame(conn)
if err != nil {
return E.Cause(err, "read client handshake")
}
_, err = handshakeConn.Write(clientHelloFrame.Bytes())
if err != nil {
clientHelloFrame.Release()
return E.Cause(err, "write client handshake")
}
err = shadowtls.VerifyClientHello(clientHelloFrame.Bytes(), s.password)
if err != nil {
s.logger.WarnContext(ctx, E.Cause(err, "client hello verify failed"))
return bufio.CopyConn(ctx, conn, handshakeConn)
}
s.logger.TraceContext(ctx, "client hello verify success")
clientHelloFrame.Release()

var serverHelloFrame *buf.Buffer
serverHelloFrame, err = shadowtls.ExtractFrame(handshakeConn)
if err != nil {
return E.Cause(err, "read server handshake")
}

_, err = conn.Write(serverHelloFrame.Bytes())
if err != nil {
serverHelloFrame.Release()
return E.Cause(err, "write server handshake")
}

serverRandom := shadowtls.ExtractServerRandom(serverHelloFrame.Bytes())

if serverRandom == nil {
s.logger.WarnContext(ctx, "server random extract failed, will copy bidirectional")
return bufio.CopyConn(ctx, conn, handshakeConn)
}

if !shadowtls.IsServerHelloSupportTLS13(serverHelloFrame.Bytes()) {
s.logger.WarnContext(ctx, "TLS 1.3 is not supported, will copy bidirectional")
return bufio.CopyConn(ctx, conn, handshakeConn)
}

serverHelloFrame.Release()
s.logger.TraceContext(ctx, "client authenticated. server random extracted: ", hex.EncodeToString(serverRandom))

hmacWrite := hmac.New(sha1.New, []byte(s.password))
hmacWrite.Write(serverRandom)

hmacAdd := hmac.New(sha1.New, []byte(s.password))
hmacAdd.Write(serverRandom)
hmacAdd.Write([]byte("S"))

hmacVerify := hmac.New(sha1.New, []byte(s.password))
hmacVerifyReset := func() {
hmacVerify.Reset()
hmacVerify.Write(serverRandom)
hmacVerify.Write([]byte("C"))
}

var clientFirstFrame *buf.Buffer
var group task.Group
var handshakeFinished bool
group.Append("client handshake relay", func(ctx context.Context) error {
clientFrame, cErr := shadowtls.CopyByFrameUntilHMACMatches(conn, handshakeConn, hmacVerify, hmacVerifyReset)
if cErr == nil {
clientFirstFrame = clientFrame
handshakeFinished = true
handshakeConn.Close()
}
return cErr
})
group.Append("server handshake relay", func(ctx context.Context) error {
cErr := shadowtls.CopyByFrameWithModification(handshakeConn, conn, s.password, serverRandom, hmacWrite)
if E.IsClosedOrCanceled(cErr) && handshakeFinished {
return nil
}
return cErr
})
group.Cleanup(func() {
handshakeConn.Close()
})
err = group.Run(ctx)
if err != nil {
return E.Cause(err, "handshake relay")
}

s.logger.TraceContext(ctx, "handshake relay finished")
return s.newConnection(ctx, bufio.NewCachedConn(shadowtls.NewVerifiedConn(conn, hmacAdd, hmacVerify, nil), clientFirstFrame), metadata)
}
}

Expand Down
42 changes: 37 additions & 5 deletions outbound/shadowtls.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package outbound

import (
"context"
"crypto/hmac"
"crypto/sha1"
"net"
"os"

Expand All @@ -25,7 +27,7 @@ type ShadowTLS struct {
dialer N.Dialer
serverAddr M.Socksaddr
tlsConfig tls.Config
v2 bool
version int
password string
}

Expand All @@ -45,19 +47,26 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
outbound.version = options.Version
switch options.Version {
case 0:
fallthrough
case 1:
options.TLS.MinVersion = "1.2"
options.TLS.MaxVersion = "1.2"
case 2:
outbound.v2 = true
case 3:
options.TLS.MinVersion = "1.3"
options.TLS.MaxVersion = "1.3"
default:
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
}
var err error
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
if options.Version != 3 {
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
} else {
outbound.tlsConfig, err = shadowtls.NewClientTLSConfig(options.Server, common.PtrValueOrDefault(options.TLS), options.Password)
}
if err != nil {
return nil, err
}
Expand All @@ -74,19 +83,42 @@ func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination
if err != nil {
return nil, err
}
if !s.v2 {
switch s.version {
default:
fallthrough
case 1:
_, err = tls.ClientHandshake(ctx, conn, s.tlsConfig)
if err != nil {
return nil, err
}
return conn, nil
} else {
case 2:
hashConn := shadowtls.NewHashReadConn(conn, s.password)
_, err = tls.ClientHandshake(ctx, hashConn, s.tlsConfig)
if err != nil {
return nil, err
}
return shadowtls.NewClientConn(hashConn), nil
case 3:
streamWrapper := shadowtls.NewStreamWrapper(conn, s.password)
_, err = tls.ClientHandshake(ctx, streamWrapper, s.tlsConfig)
if err != nil {
return nil, err
}
authorized, serverRandom, readHMAC := streamWrapper.Authorized()
if !authorized {
return nil, E.New("traffic hijacked or TLS1.3 is not supported")
}

hmacAdd := hmac.New(sha1.New, []byte(s.password))
hmacAdd.Write(serverRandom)
hmacAdd.Write([]byte("C"))

hmacVerify := hmac.New(sha1.New, []byte(s.password))
hmacVerify.Write(serverRandom)
hmacVerify.Write([]byte("S"))

return shadowtls.NewVerifiedConn(conn, hmacAdd, hmacVerify, readHMAC), nil
}
}

Expand Down
Loading

0 comments on commit 21cb227

Please sign in to comment.