Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ errorgen
*.dat
*~
[._]*.un~

.golangci.yml
3 changes: 3 additions & 0 deletions transport/internet/tlsmirror/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type TLSRecord struct {
LegacyProtocolVersion [2]byte
RecordLength uint16
Fragment []byte

// Annotations are used to store additional information about the record. Never sent over the wire.
InsertedMessage bool
}

type RecordReader interface {
Expand Down
33 changes: 32 additions & 1 deletion transport/internet/tlsmirror/mirrorbase/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import (

// NewMirroredTLSConn creates a new mirrored TLS connection.
// No stable interface
func NewMirroredTLSConn(ctx context.Context, clientConn net.Conn, serverConn net.Conn, onC2SMessage, onS2CMessage tlsmirror.MessageHook, closable common.Closable, explicitNonceDetection tlsmirror.ExplicitNonceDetection) tlsmirror.InsertableTLSConn {
func NewMirroredTLSConn(ctx context.Context, clientConn net.Conn,
serverConn net.Conn, onC2SMessage, onS2CMessage tlsmirror.MessageHook,
closable common.Closable, explicitNonceDetection tlsmirror.ExplicitNonceDetection,
onC2SMessageTx, onS2CMessageTx tlsmirror.MessageHook,
) tlsmirror.InsertableTLSConn {
explicitNonceDetectionReady, explicitNonceDetectionOver := context.WithCancel(ctx)
c := &conn{
ctx: ctx,
Expand All @@ -28,6 +32,8 @@ func NewMirroredTLSConn(ctx context.Context, clientConn net.Conn, serverConn net
explicitNonceDetection: explicitNonceDetection,
explicitNonceDetectionReady: explicitNonceDetectionReady,
explicitNonceDetectionOver: explicitNonceDetectionOver,
OnC2SMessageTx: onC2SMessageTx,
OnS2CMessageTx: onS2CMessageTx,
}
c.ctx, c.done = context.WithCancel(ctx)
go c.c2sWorker()
Expand All @@ -54,6 +60,9 @@ type conn struct {
OnS2CMessage tlsmirror.MessageHook
explicitNonceDetection tlsmirror.ExplicitNonceDetection

OnC2SMessageTx tlsmirror.MessageHook
OnS2CMessageTx tlsmirror.MessageHook

c2sInsert chan *tlsmirror.TLSRecord
s2cInsert chan *tlsmirror.TLSRecord

Expand Down Expand Up @@ -182,6 +191,17 @@ func (c *conn) c2sWorker() {
}
}
}
if c.OnC2SMessageTx != nil {
drop, err := c.OnC2SMessageTx(record)
if err != nil {
c.done()
newError("failed to process C2S message").Base(err).AtWarning().WriteToLog()
return
}
if drop {
continue
}
}
err := recordWriter.WriteRecord(record, false)
if err != nil {
c.done()
Expand Down Expand Up @@ -327,6 +347,17 @@ func (c *conn) s2cWorker() {
}
}
}
if c.OnS2CMessageTx != nil {
drop, err := c.OnS2CMessageTx(record)
if err != nil {
c.done()
newError("failed to process S2C message").Base(err).AtWarning().WriteToLog()
return
}
if drop {
continue
}
}
err := recordWriter.WriteRecord(record, false)
if err != nil {
c.done()
Expand Down
28 changes: 28 additions & 0 deletions transport/internet/tlsmirror/mirrorcrypto/derive_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,31 @@ func DeriveSecondaryKey(primaryKey []byte, tag string) ([]byte, error) {

return secondaryKey, nil
}

func DeriveSequenceWatermarkingKey(primaryKey, clientRandom, serverRandom []byte, tag string) ([]byte, []byte, error) {
if len(primaryKey) != 32 {
return nil, nil, newError("invalid primary key size: ", len(primaryKey))
}
if len(clientRandom) != 32 {
return nil, nil, newError("invalid client random size: ", len(clientRandom))
}
if len(serverRandom) != 32 {
return nil, nil, newError("invalid server random size: ", len(serverRandom))
}

// Concatenate the primary key, client random, and server random
combined := append(primaryKey, clientRandom...) // nolint: gocritic
combined = append(combined, serverRandom...)

encryptionKey, err := hkdf.Expand(sha256.New, combined, "v2ray-xv64FXUU-GxMn8UYz-bTy6UDeE:tlsmirror-sequence-watermark"+tag, 32)
if err != nil {
return nil, nil, newError("unable to derive encryption key").Base(err)
}

nonceMask, err := hkdf.Expand(sha256.New, combined, "v2ray-xv64FXUU-GxMn8UYz-bTy6UDeE:tlsmirror-sequence-watermark"+tag, 24)
if err != nil {
return nil, nil, newError("unable to derive nonce mask").Base(err)
}

return encryptionKey, nonceMask, nil
}
23 changes: 12 additions & 11 deletions transport/internet/tlsmirror/server/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,21 @@ func (d *persistentMirrorTLSDialer) handleIncomingCarrierConnection(ctx context.

ctx, cancel := context.WithCancel(ctx)
cconnState := &clientConnState{
ctx: ctx,
done: cancel,
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
handler: d.handleIncomingReadyConnection,
primaryKey: d.config.PrimaryKey,
readPipe: make(chan []byte, 1),
firstWrite: true,
firstWriteDelay: firstWriteDelay,
transportLayerPadding: d.config.TransportLayerPadding,
ctx: ctx,
done: cancel,
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
handler: d.handleIncomingReadyConnection,
primaryKey: d.config.PrimaryKey,
readPipe: make(chan []byte, 1),
firstWrite: true,
firstWriteDelay: firstWriteDelay,
transportLayerPadding: d.config.TransportLayerPadding,
sequenceWatermarkEnabled: d.config.SequenceWatermarkingEnabled,
}

cconnState.mirrorConn = mirrorbase.NewMirroredTLSConn(ctx, conn, forwardConn, cconnState.onC2SMessage, cconnState.onS2CMessage, conn,
d.explicitNonceCiphersuiteLookup.Lookup)
d.explicitNonceCiphersuiteLookup.Lookup, cconnState.onC2SMessageTx, cconnState.onS2CMessageTx)
}

type connectionContextGetter interface {
Expand Down
69 changes: 69 additions & 0 deletions transport/internet/tlsmirror/server/client_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package server
import (
"bytes"
"context"
"crypto/cipher"
gonet "net"
"time"

"golang.org/x/crypto/chacha20"

"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror"
Expand Down Expand Up @@ -38,6 +41,9 @@ type clientConnState struct {
firstWriteDelay time.Duration

transportLayerPadding *TransportLayerPadding

sequenceWatermarkEnabled bool
sequenceWatermarkTx, sequenceWatermarkRx cipher.Stream
}

func (s *clientConnState) GetConnectionContext() context.Context {
Expand Down Expand Up @@ -161,6 +167,16 @@ func (s *clientConnState) onC2SMessage(message *tlsmirror.TLSRecord) (drop bool,
}

func (s *clientConnState) onS2CMessage(message *tlsmirror.TLSRecord) (drop bool, ok error) {
if s.sequenceWatermarkEnabled {
if s.sequenceWatermarkRx != nil {
if (message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data ||
message.RecordType == mirrorcommon.TLSRecord_RecordType_alert) && len(message.Fragment) >= 16 {
watermarkRegion := message.Fragment[len(message.Fragment)-16:]
s.sequenceWatermarkRx.XORKeyStream(watermarkRegion, watermarkRegion)
}
}
}

if message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data {
explicitNonceReservedOverheadHeaderLength, err := s.mirrorConn.GetApplicationDataExplicitNonceReservedOverheadHeaderLength()
if err != nil {
Expand All @@ -176,6 +192,24 @@ func (s *clientConnState) onS2CMessage(message *tlsmirror.TLSRecord) (drop bool,
return false, nil
}

if s.sequenceWatermarkEnabled && s.sequenceWatermarkRx == nil {
clientRandom, serverRandom, err := s.mirrorConn.GetHandshakeRandom()
if err != nil {
newError("failed to get handshake random").Base(err).AtError().WriteToLog()
return true, nil
}
key, nonce, err := mirrorcrypto.DeriveSequenceWatermarkingKey(s.primaryKey, clientRandom, serverRandom, ":s2c")
if err != nil {
newError("failed to derive sequence watermarking key").Base(err).AtError().WriteToLog()
return true, nil
}
s.sequenceWatermarkRx, err = chacha20.NewUnauthenticatedCipher(key, nonce)
if err != nil {
newError("failed to create sequence watermarking cipher").Base(err).AtError().WriteToLog()
return true, nil
}
}

s.readPipe <- buffer
return true, nil
}
Expand All @@ -197,6 +231,7 @@ func (s *clientConnState) WriteMessage(message []byte) error {
LegacyProtocolVersion: s.protocolVersion,
RecordLength: uint16(len(buffer)),
Fragment: buffer,
InsertedMessage: true,
}
return s.mirrorConn.InsertC2SMessage(&record)
}
Expand Down Expand Up @@ -231,3 +266,37 @@ func (s *clientConnState) VerifyConnectionEnrollmentWithProcessor(connectionEnro
}
return nil
}

func (s *clientConnState) onC2SMessageTx(message *tlsmirror.TLSRecord) (drop bool, ok error) {
if s.sequenceWatermarkEnabled {
if s.sequenceWatermarkTx != nil {
if (message.RecordType == mirrorcommon.TLSRecord_RecordType_application_data ||
message.RecordType == mirrorcommon.TLSRecord_RecordType_alert) && len(message.Fragment) >= 16 {
watermarkRegion := message.Fragment[len(message.Fragment)-16:]
s.sequenceWatermarkTx.XORKeyStream(watermarkRegion, watermarkRegion)
}
}
if message.InsertedMessage && s.sequenceWatermarkTx == nil {
clientRandom, serverRandom, err := s.mirrorConn.GetHandshakeRandom()
if err != nil {
newError("failed to get handshake random").Base(err).AtError().WriteToLog()
return true, nil
}
key, nonce, err := mirrorcrypto.DeriveSequenceWatermarkingKey(s.primaryKey, clientRandom, serverRandom, ":c2s")
if err != nil {
newError("failed to derive sequence watermarking key").Base(err).AtError().WriteToLog()
return true, nil
}
s.sequenceWatermarkTx, err = chacha20.NewUnauthenticatedCipher(key, nonce)
if err != nil {
newError("failed to create sequence watermarking cipher").Base(err).AtError().WriteToLog()
return true, nil
}
}
}
return false, nil
}

func (s *clientConnState) onS2CMessageTx(message *tlsmirror.TLSRecord) (drop bool, ok error) {
return false, nil
}
13 changes: 11 additions & 2 deletions transport/internet/tlsmirror/server/config.pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ type Config struct {
DeferInstanceDerivedWriteTime *TimeSpec `protobuf:"bytes,8,opt,name=defer_instance_derived_write_time,json=deferInstanceDerivedWriteTime,proto3" json:"defer_instance_derived_write_time,omitempty"`
TransportLayerPadding *TransportLayerPadding `protobuf:"bytes,9,opt,name=transport_layer_padding,json=transportLayerPadding,proto3" json:"transport_layer_padding,omitempty"`
ConnectionEnrollment *mirrorenrollment.Config `protobuf:"bytes,10,opt,name=connection_enrollment,json=connectionEnrollment,proto3" json:"connection_enrollment,omitempty"`
SequenceWatermarkingEnabled bool `protobuf:"varint,11,opt,name=sequence_watermarking_enabled,json=sequenceWatermarkingEnabled,proto3" json:"sequence_watermarking_enabled,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
Expand Down Expand Up @@ -230,6 +231,13 @@ func (x *Config) GetConnectionEnrollment() *mirrorenrollment.Config {
return nil
}

func (x *Config) GetSequenceWatermarkingEnabled() bool {
if x != nil {
return x.SequenceWatermarkingEnabled
}
return false
}

var File_transport_internet_tlsmirror_server_config_proto protoreflect.FileDescriptor

const file_transport_internet_tlsmirror_server_config_proto_rawDesc = "" +
Expand All @@ -239,7 +247,7 @@ const file_transport_internet_tlsmirror_server_config_proto_rawDesc = "" +
"\x10base_nanoseconds\x18\x01 \x01(\x04R\x0fbaseNanoseconds\x12Q\n" +
"%uniform_random_multiplier_nanoseconds\x18\x02 \x01(\x04R\"uniformRandomMultiplierNanoseconds\"1\n" +
"\x15TransportLayerPadding\x12\x18\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\"\xad\x06\n" +
"\aenabled\x18\x01 \x01(\bR\aenabled\"\xf1\x06\n" +
"\x06Config\x12'\n" +
"\x0fforward_address\x18\x01 \x01(\tR\x0eforwardAddress\x12!\n" +
"\fforward_port\x18\x02 \x01(\rR\vforwardPort\x12\x1f\n" +
Expand All @@ -253,7 +261,8 @@ const file_transport_internet_tlsmirror_server_config_proto_rawDesc = "" +
"!defer_instance_derived_write_time\x18\b \x01(\v28.v2ray.core.transport.internet.tlsmirror.server.TimeSpecR\x1ddeferInstanceDerivedWriteTime\x12}\n" +
"\x17transport_layer_padding\x18\t \x01(\v2E.v2ray.core.transport.internet.tlsmirror.server.TransportLayerPaddingR\x15transportLayerPadding\x12u\n" +
"\x15connection_enrollment\x18\n" +
" \x01(\v2@.v2ray.core.transport.internet.tlsmirror.mirrorenrollment.ConfigR\x14connectionEnrollment:'\x82\xb5\x18#\n" +
" \x01(\v2@.v2ray.core.transport.internet.tlsmirror.mirrorenrollment.ConfigR\x14connectionEnrollment\x12B\n" +
"\x1dsequence_watermarking_enabled\x18\v \x01(\bR\x1bsequenceWatermarkingEnabled:'\x82\xb5\x18#\n" +
"\ttransport\x12\ttlsmirror\x8a\xff)\ttlsmirrorB\xab\x01\n" +
"2com.v2ray.core.transport.internet.tlsmirror.serverP\x01ZBgithub.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/server\xaa\x02.V2Ray.Core.Transport.Internet.Tlsmirror.Serverb\x06proto3"

Expand Down
2 changes: 2 additions & 0 deletions transport/internet/tlsmirror/server/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ message Config {

v2ray.core.transport.internet.tlsmirror.mirrorenrollment.Config connection_enrollment = 10;

bool sequence_watermarking_enabled = 11;

}
Loading
Loading