Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
120 changes: 120 additions & 0 deletions ciphers/aead_2022_cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ciphers

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"sync"
"time"
)

type CipherConf2022 struct {
KeyLen int
SaltLen int
NonceLen int
TagLen int
NewCipher func(key []byte) (cipher.AEAD, error)
NewBlockCipher func(key []byte) (cipher.Block, error)
}

const (
// Timestamp tolerance
TimestampTolerance = 30 * time.Second

// Salt storage duration
SaltStorageDuration = 60 * time.Second
)

var (
Aead2022CiphersConf = map[string]*CipherConf2022{
"2022-blake3-aes-256-gcm": {KeyLen: 32, SaltLen: 32, NonceLen: 12, TagLen: 16, NewCipher: NewGcm, NewBlockCipher: aes.NewCipher},
"2022-blake3-aes-128-gcm": {KeyLen: 16, SaltLen: 16, NonceLen: 12, TagLen: 16, NewCipher: NewGcm, NewBlockCipher: aes.NewCipher},
}
)

// ValidateBase64PSK validates that the PSK is a valid base64 string with correct length
func ValidateBase64PSK(pskBase64 string, expectedKeyLen int) ([]byte, error) {
if pskBase64 == "" {
return nil, fmt.Errorf("PSK cannot be empty for SIP022 methods")
}

psk, err := base64.StdEncoding.DecodeString(pskBase64)
if err != nil {
return nil, fmt.Errorf("PSK must be valid base64 for SIP022 methods: %w", err)
}

if len(psk) != expectedKeyLen {
return nil, fmt.Errorf("PSK length must be %d bytes for this method, got %d", expectedKeyLen, len(psk))
}

return psk, nil
}

// SlidingWindowFilter implements a sliding window filter for packet ID replay protection
type SlidingWindowFilter struct {
window []uint64
windowSize int
latest uint64
mutex sync.RWMutex
}

// NewSlidingWindowFilter creates a new sliding window filter
func NewSlidingWindowFilter(windowSize int) *SlidingWindowFilter {
return &SlidingWindowFilter{
window: make([]uint64, windowSize),
windowSize: windowSize,
}
}
Comment on lines +64 to +73

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug in sliding window implementation: on line 65, the window is allocated with size 'windowSize', but the implementation appears to treat the window as an array of 64-bit words where each bit represents a packet ID. Line 108 calculates 'wordIndex := index / 64', suggesting the window should have size 'windowSize / 64' (or '(windowSize + 63) / 64' to round up). The current implementation may cause array index out of bounds when index >= 64 * len(f.window).

Copilot uses AI. Check for mistakes.

// CheckAndUpdate checks if the packet ID is valid and updates the window
func (f *SlidingWindowFilter) CheckAndUpdate(packetID uint64) bool {
f.mutex.Lock()
defer f.mutex.Unlock()

// Packet ID too old
if packetID+uint64(f.windowSize) <= f.latest {
return false
}

// Packet ID in the future, update latest
if packetID > f.latest {
// Shift window
shift := packetID - f.latest
if shift >= uint64(f.windowSize) {
// Clear entire window
for i := range f.window {
f.window[i] = 0
}
} else {
// Shift window by 'shift' positions
for i := 0; i < len(f.window)-int(shift); i++ {
f.window[i] = f.window[i+int(shift)]
}
for i := len(f.window) - int(shift); i < len(f.window); i++ {
f.window[i] = 0
}
}
f.latest = packetID
return true
}

// Packet ID in the window
index := int(f.latest - packetID)
if index >= f.windowSize {
return false
}

wordIndex := index / 64
bitIndex := index % 64
mask := uint64(1) << bitIndex

// Check if already seen
if f.window[wordIndex]&mask != 0 {
return false
}

// Mark as seen
f.window[wordIndex] |= mask
return true
}
9 changes: 5 additions & 4 deletions ciphers/aead_cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ var (
"aes-256-gcm": {KeyLen: 32, SaltLen: 32, NonceLen: 12, TagLen: 16, NewCipher: NewGcm},
"aes-128-gcm": {KeyLen: 16, SaltLen: 16, NonceLen: 12, TagLen: 16, NewCipher: NewGcm},
}
ZeroNonce [MaxNonceSize]byte
ShadowsocksReusedInfo = []byte("ss-subkey")
JuicityReusedInfo = []byte("juicity-reused-info")
ZeroNonce [MaxNonceSize]byte
JuicityReusedInfo = []byte("juicity-reused-info")
)

func NewGcm(key []byte) (cipher.AEAD, error) {
Expand All @@ -46,7 +45,9 @@ func NewGcm(key []byte) (cipher.AEAD, error) {
return cipher.NewGCM(block)
}

// Verify is used for legacy compatibility
func (conf *CipherConf) Verify(buf []byte, masterKey []byte, salt []byte, cipherText []byte, subKey *[]byte) ([]byte, bool) {
var shadowsocksReusedInfo = []byte("ss-subkey")
var sk []byte
if subKey != nil && len(*subKey) == conf.KeyLen {
sk = *subKey
Expand All @@ -57,7 +58,7 @@ func (conf *CipherConf) Verify(buf []byte, masterKey []byte, salt []byte, cipher
sha1.New,
masterKey,
salt,
ShadowsocksReusedInfo,
shadowsocksReusedInfo,
)
io.ReadFull(kdf, sk)
if subKey != nil && cap(*subKey) >= conf.KeyLen {
Expand Down
6 changes: 2 additions & 4 deletions dialer/shadowsocks/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,13 @@ import (
"github.com/daeuniverse/outbound/dialer"
"github.com/daeuniverse/outbound/netproxy"
"github.com/daeuniverse/outbound/protocol"
"github.com/daeuniverse/outbound/protocol/shadowsocks"
"github.com/daeuniverse/outbound/transport/mux"
"github.com/daeuniverse/outbound/transport/simpleobfs"
"github.com/daeuniverse/outbound/transport/tls"
"github.com/daeuniverse/outbound/transport/ws"
)

func init() {
// Use random salt by default to decrease the boot time
shadowsocks.DefaultSaltGeneratorType = shadowsocks.RandomSaltGeneratorType

dialer.FromLinkRegister("shadowsocks", NewShadowsocksFromLink)
dialer.FromLinkRegister("ss", NewShadowsocksFromLink)
}
Expand Down Expand Up @@ -119,6 +115,8 @@ func (s *Shadowsocks) Dialer(option *dialer.ExtraOption, nextDialer netproxy.Dia
switch s.Cipher {
case "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305":
nextDialerName = "shadowsocks"
case "2022-blake3-aes-256-gcm", "2022-blake3-aes-128-gcm":
nextDialerName = "shadowsocks_2022"
case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "des-cfb", "bf-cfb", "cast5-cfb", "rc4-md5", "rc4-md5-6", "chacha20", "chacha20-ietf", "salsa20", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "rc4", "none", "plain":
nextDialerName = "shadowsocks_stream"
default:
Expand Down
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ require (
github.com/json-iterator/go v1.1.12
github.com/mzz2017/disk-bloom v1.0.1
github.com/refraction-networking/utls v1.6.4
github.com/samber/oops v1.19.4
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.11.1
gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37
golang.org/x/crypto v0.33.0
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
golang.org/x/net v0.34.0
golang.org/x/sys v0.30.0
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.36.1
lukechampine.com/blake3 v1.4.1
)

require (
Expand All @@ -40,11 +42,16 @@ require (
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oklog/ulid/v2 v2.1.1 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/samber/lo v1.52.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
go.uber.org/mock v0.5.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
Expand Down
19 changes: 17 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,32 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M=
github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/refraction-networking/utls v1.6.4 h1:aeynTroaYn7y+mFtqv8D0bQ4bw0y9nJHneGxJ7lvRDM=
github.com/refraction-networking/utls v1.6.4/go.mod h1:2VL2xfiqgFAZtJKeUTlf+PSYFs3Eu7km0gCtXJ3m8zs=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/samber/oops v1.19.4 h1:NMzXd3JtdJ4IM2dJgJ4/W8V2bljeCACKYRfPg9vWjeg=
github.com/samber/oops v1.19.4/go.mod h1:Hsm/sKPxtCfPh0w/cE3xVoRfSiE1joDRiStPAsmG9bo=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
Expand All @@ -73,10 +82,14 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37 h1:ZrWBE3u/o9cHU2mySXf1687MaK09JOeZt1A+fHnCjmU=
gitlab.com/yawning/chacha20.git v0.0.0-20230427033715-7877545b1b37/go.mod h1:3x6b94nWCP/a2XB/joOPMiGYUBvqbLfeY/BkHLeDs6s=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down Expand Up @@ -123,3 +136,5 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
Loading
Loading