VLESS Encryption: Randomize seconds in ticket and simplify expiration mechanism

https://github.com/XTLS/Xray-core/pull/5067#issuecomment-3246925902
dependabot/github_actions/actions/github-script-8
RPRX 2025-09-02 23:37:14 +00:00 committed by GitHub
parent e943de5300
commit 19f8907296
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 74 deletions

View File

@ -96,16 +96,18 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
default: default:
return false return false
} }
if s[2] != "1rtt" { t := strings.SplitN(strings.TrimSuffix(s[2], "s"), "-", 2)
t := strings.TrimSuffix(s[2], "s") i, err := strconv.Atoi(t[0])
if t == s[2] { if err != nil {
return false return false
} }
i, err := strconv.Atoi(t) config.SecondsFrom = uint32(i)
if len(t) > 1 {
i, err := strconv.Atoi(t[1])
if err != nil { if err != nil {
return false return false
} }
config.Seconds = uint32(i) config.SecondsTo = uint32(i)
} }
padding := 0 padding := 0
for _, r := range s[3:] { for _, r := range s[3:] {

View File

@ -18,7 +18,6 @@ import (
) )
type ServerSession struct { type ServerSession struct {
Expire time.Time
PfsKey []byte PfsKey []byte
NfsKeys sync.Map NfsKeys sync.Map
} }
@ -29,16 +28,16 @@ type ServerInstance struct {
Hash32s [][32]byte Hash32s [][32]byte
RelaysLength int RelaysLength int
XorMode uint32 XorMode uint32
Seconds uint32 SecondsFrom uint32
SecondsTo uint32
PaddingLens [][3]int PaddingLens [][3]int
PaddingGaps [][3]int PaddingGaps [][3]int
RWLock sync.RWMutex RWLock sync.RWMutex
Sessions map[[16]byte]*ServerSession Sessions map[[16]byte]*ServerSession
Closed bool
} }
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) { func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, secondsFrom, secondsTo uint32, padding string) (err error) {
if i.NfsSKeys != nil { if i.NfsSKeys != nil {
return errors.New("already initialized") return errors.New("already initialized")
} }
@ -67,37 +66,12 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, p
} }
i.RelaysLength -= 32 i.RelaysLength -= 32
i.XorMode = xorMode i.XorMode = xorMode
if seconds > 0 { i.SecondsFrom = secondsFrom
i.Seconds = seconds i.SecondsTo = secondsTo
i.Sessions = make(map[[16]byte]*ServerSession) i.Sessions = make(map[[16]byte]*ServerSession)
go func() {
for {
time.Sleep(time.Minute)
i.RWLock.Lock()
if i.Closed {
i.RWLock.Unlock()
return
}
now := time.Now()
for ticket, session := range i.Sessions {
if now.After(session.Expire) {
delete(i.Sessions, ticket)
}
}
i.RWLock.Unlock()
}
}()
}
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps) return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
} }
func (i *ServerInstance) Close() (err error) {
i.RWLock.Lock()
i.Closed = true
i.RWLock.Unlock()
return
}
func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) { func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) {
if i.NfsSKeys == nil { if i.NfsSKeys == nil {
return nil, errors.New("uninitialized") return nil, errors.New("uninitialized")
@ -132,7 +106,7 @@ func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn
return nil, err return nil, err
} }
if publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security if publicKey.Bytes()[31] > 127 { // we just don't want the observer can change even one bit without breaking the connection, though it has nothing to do with security
return nil, errors.New("the highest bit of the last byte of the peer-sent X25519 public key must be 0") return nil, errors.New("the highest bit of the last byte of the peer-sent X25519 public key is not 0")
} }
nfsKey, err = k.ECDH(publicKey) nfsKey, err = k.ECDH(publicKey)
if err != nil { if err != nil {
@ -180,7 +154,7 @@ func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn
length := DecodeLength(decryptedLength) length := DecodeLength(decryptedLength)
if length == 32 { if length == 32 {
if i.Seconds == 0 { if i.SecondsFrom == 0 && i.SecondsTo == 0 {
return nil, errors.New("0-RTT is not allowed") return nil, errors.New("0-RTT is not allowed")
} }
encryptedTicket := make([]byte, 32) encryptedTicket := make([]byte, 32)
@ -252,14 +226,23 @@ func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn
ticket := make([]byte, 16) ticket := make([]byte, 16)
rand.Read(ticket) rand.Read(ticket)
copy(ticket, EncodeLength(int(i.Seconds*4/5))) seconds := 0
if i.Seconds > 0 { if i.SecondsTo == 0 {
seconds = int(i.SecondsFrom) * int(crypto.RandBetween(50, 100)) / 100
} else {
seconds = int(crypto.RandBetween(int64(i.SecondsFrom), int64(i.SecondsTo)))
}
copy(ticket, EncodeLength(int(seconds)))
if seconds > 0 {
i.RWLock.Lock() i.RWLock.Lock()
i.Sessions[[16]byte(ticket)] = &ServerSession{ i.Sessions[[16]byte(ticket)] = &ServerSession{PfsKey: pfsKey}
Expire: time.Now().Add(time.Duration(i.Seconds) * time.Second),
PfsKey: pfsKey,
}
i.RWLock.Unlock() i.RWLock.Unlock()
go func() {
time.Sleep(time.Duration(seconds)*time.Second + time.Minute)
i.RWLock.Lock()
delete(i.Sessions, [16]byte(ticket))
i.RWLock.Unlock()
}()
} }
pfsKeyExchangeLength := 1088 + 32 + 16 pfsKeyExchangeLength := 1088 + 32 + 16

View File

@ -111,12 +111,13 @@ type Config struct {
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"` Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"` Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"` Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"` XorMode uint32 `protobuf:"varint,4,opt,name=xorMode,proto3" json:"xorMode,omitempty"`
Seconds uint32 `protobuf:"varint,5,opt,name=seconds,proto3" json:"seconds,omitempty"` SecondsFrom uint32 `protobuf:"varint,5,opt,name=seconds_from,json=secondsFrom,proto3" json:"seconds_from,omitempty"`
Padding string `protobuf:"bytes,6,opt,name=padding,proto3" json:"padding,omitempty"` SecondsTo uint32 `protobuf:"varint,6,opt,name=seconds_to,json=secondsTo,proto3" json:"seconds_to,omitempty"`
Padding string `protobuf:"bytes,7,opt,name=padding,proto3" json:"padding,omitempty"`
} }
func (x *Config) Reset() { func (x *Config) Reset() {
@ -177,9 +178,16 @@ func (x *Config) GetXorMode() uint32 {
return 0 return 0
} }
func (x *Config) GetSeconds() uint32 { func (x *Config) GetSecondsFrom() uint32 {
if x != nil { if x != nil {
return x.Seconds return x.SecondsFrom
}
return 0
}
func (x *Config) GetSecondsTo() uint32 {
if x != nil {
return x.SecondsTo
} }
return 0 return 0
} }
@ -207,7 +215,7 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xee, 0x01, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0x96, 0x02,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
@ -219,18 +227,20 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x12, 0x18, 0x0a, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x0d, 0x52, 0x07, 0x78, 0x6f, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65,
0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x73, 0x65, 0x63, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d,
0x6f, 0x6e, 0x64, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x52, 0x0b, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x1d, 0x0a,
0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, 0x0a, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x74, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28,
0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x0d, 0x52, 0x09, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x54, 0x6f, 0x12, 0x18, 0x0a, 0x07,
0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70,
0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69,
0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63,
0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f,
0x6f, 0x33, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View File

@ -23,6 +23,7 @@ message Config {
string decryption = 3; string decryption = 3;
uint32 xorMode = 4; uint32 xorMode = 4;
uint32 seconds = 5; uint32 seconds_from = 5;
string padding = 6; uint32 seconds_to = 6;
string padding = 7;
} }

View File

@ -93,7 +93,7 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
nfsSKeysBytes = append(nfsSKeysBytes, b) nfsSKeysBytes = append(nfsSKeysBytes, b)
} }
handler.decryption = &encryption.ServerInstance{} handler.decryption = &encryption.ServerInstance{}
if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.Seconds, config.Padding); err != nil { if err := handler.decryption.Init(nfsSKeysBytes, config.XorMode, config.SecondsFrom, config.SecondsTo, config.Padding); err != nil {
return nil, errors.New("failed to use decryption").Base(err).AtError() return nil, errors.New("failed to use decryption").Base(err).AtError()
} }
} }
@ -176,9 +176,6 @@ func isMuxAndNotXUDP(request *protocol.RequestHeader, first *buf.Buffer) bool {
// Close implements common.Closable.Close(). // Close implements common.Closable.Close().
func (h *Handler) Close() error { func (h *Handler) Close() error {
if h.decryption != nil {
h.decryption.Close()
}
return errors.Combine(common.Close(h.validator)) return errors.Combine(common.Close(h.validator))
} }