Skip to content

Commit

Permalink
feat: allow unencoded userinfo as per SIP002 (#260)
Browse files Browse the repository at this point in the history
* feat: allow unencoded `userinfo` as per SIP002

* Move shadowsocks config tests to `shadowsocks_test.go` and clean up.

* Review comment.

* Add test case for invalid cipher info (e.g. missin colon).

* Remove unnecessary prefix in test.
  • Loading branch information
sbruens authored Jul 16, 2024
1 parent 888e11d commit e157b30
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 65 deletions.
61 changes: 0 additions & 61 deletions x/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package config

import (
"encoding/base64"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -55,69 +54,9 @@ func TestSanitizeConfig(t *testing.T) {
require.Error(t, err)
}

func TestShowsocksLagacyBase64URL(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))
config, err := parseShadowsocksLegacyBase64URL(urls[0])
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
require.NoError(t, err)
}

func TestParseShadowsocksURL(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))
config, err := parseShadowsocksURL(urls[0])
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
require.NoError(t, err)

encoded = base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567"))
urls, err = parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))
config, err = parseShadowsocksURL(urls[0])
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
require.NoError(t, err)
}

func TestSocks5URLSanitization(t *testing.T) {
configString := "socks5://myuser:mypassword@192.168.1.100:1080"
sanitizedConfig, err := SanitizeConfig(configString)
require.NoError(t, err)
require.Equal(t, "socks5://REDACTED@192.168.1.100:1080", sanitizedConfig)
}

func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))
_, err = parseShadowsocksSIP002URL(urls[0])
require.Error(t, err)
}

func TestParseShadowsocksSIP002URLUnsupportedCypher(t *testing.T) {
configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwnTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234?prefix=HTTP%2F1.1%20"
urls, err := parseConfig(configString)
require.NoError(t, err)
require.Equal(t, 1, len(urls))
_, err = parseShadowsocksSIP002URL(urls[0])
require.Error(t, err)
}

func TestParseShadowsocksSIP002URLSuccessful(t *testing.T) {
configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234?prefix=HTTP%2F1.1%20"
urls, err := parseConfig(configString)
require.NoError(t, err)
require.Equal(t, 1, len(urls))
config, err := parseShadowsocksSIP002URL(urls[0])
require.NoError(t, err)
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
}
14 changes: 10 additions & 4 deletions x/config/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,17 @@ func parseShadowsocksSIP002URL(url *url.URL) (*shadowsocksConfig, error) {
return nil, errors.New("host not specified")
}
config.serverAddress = url.Host
cipherInfoBytes, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(url.User.String())
if err != nil {
return nil, fmt.Errorf("failed to decode cipher info [%v]: %w", url.User.String(), err)
userInfo := url.User.String()
// Cipher info can be optionally encoded with Base64URL.
encoding := base64.URLEncoding.WithPadding(base64.NoPadding)
decodedUserInfo, err := encoding.DecodeString(userInfo)
var cipherInfo string
if err == nil {
cipherInfo = string(decodedUserInfo)
} else {
cipherInfo = userInfo
}
cipherName, secret, found := strings.Cut(string(cipherInfoBytes), ":")
cipherName, secret, found := strings.Cut(cipherInfo, ":")
if !found {
return nil, errors.New("invalid cipher info: no ':' separator")
}
Expand Down
85 changes: 85 additions & 0 deletions x/config/shadowsocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package config

import (
"encoding/base64"
"net/url"
"testing"

Expand All @@ -36,3 +37,87 @@ func Test_sanitizeShadowsocksURL_withPrefix(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "ss://REDACTED@192.168.100.1:8888?prefix=foo", sanitized)
}

func TestParseShadowsocksURLFullyEncoded(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))

config, err := parseShadowsocksURL(urls[0])

require.NoError(t, err)
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
}

func TestParseShadowsocksURLUserInfoEncoded(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567"))
urls, err := parseConfig("ss://" + string(encoded) + "@example.com:1234?prefix=HTTP%2F1.1%20" + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))

config, err := parseShadowsocksURL(urls[0])

require.NoError(t, err)
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
}

func TestParseShadowsocksURLNoEncoding(t *testing.T) {
configString := "ss://aes-256-gcm:1234567@example.com:1234"
urls, err := parseConfig(configString)
require.NoError(t, err)
require.Equal(t, 1, len(urls))

config, err := parseShadowsocksURL(urls[0])

require.NoError(t, err)
require.Equal(t, "example.com:1234", config.serverAddress)
}

func TestParseShadowsocksURLInvalidCipherInfoFails(t *testing.T) {
configString := "ss://aes-256-gcm1234567@example.com:1234"
urls, err := parseConfig(configString)
require.NoError(t, err)
require.Equal(t, 1, len(urls))

_, err = parseShadowsocksURL(urls[0])

require.Error(t, err)
}

func TestParseShadowsocksURLUnsupportedCypherFails(t *testing.T) {
configString := "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwnTpLeTUyN2duU3FEVFB3R0JpQ1RxUnlT@example.com:1234"
urls, err := parseConfig(configString)
require.NoError(t, err)
require.Equal(t, 1, len(urls))

_, err = parseShadowsocksURL(urls[0])

require.Error(t, err)
}

func TestParseShadowsocksLegacyBase64URL(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))

config, err := parseShadowsocksLegacyBase64URL(urls[0])

require.NoError(t, err)
require.Equal(t, "example.com:1234", config.serverAddress)
require.Equal(t, "HTTP/1.1 ", string(config.prefix))
}

func TestParseShadowsocksSIP002URLUnsuccessful(t *testing.T) {
encoded := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString([]byte("aes-256-gcm:1234567@example.com:1234?prefix=HTTP%2F1.1%20"))
urls, err := parseConfig("ss://" + string(encoded) + "#outline-123")
require.NoError(t, err)
require.Equal(t, 1, len(urls))

_, err = parseShadowsocksSIP002URL(urls[0])

require.Error(t, err)
}

0 comments on commit e157b30

Please sign in to comment.