change url encryption format, fix easy mode json

This commit is contained in:
Page Fault
2020-07-14 15:09:44 +00:00
parent 2dc02c25e7
commit 3b63cc2db4
6 changed files with 125 additions and 61 deletions
+7 -3
View File
@@ -4,6 +4,10 @@ draft: false
weight: 200
---
## Changelog
- encrpytion 格式修改为 ss;method:password
## 概述
感谢 @DuckSoft @StudentMain @phlinhng 对 Trojan-Go URL 方案的讨论和贡献。**目前 URL 方案为草案,需要更多的实践和讨论。**
@@ -16,7 +20,7 @@ Trojan-Go**客户端**可以接受URL,以定位服务器资源。原则如下:
- URL 的作用,是定位 Trojan-Go 节点资源,方便资源分享
需要注意,基于人类可读性的考虑,禁止将 base64 等编码数据嵌入 URL 中。首先, base64 编码不能保证传输安全,其意义在于在 ASCII 信道传输非 ASCII 数据。其次,如果需要保证分享URL时的传输安全,请对明文URL进行加密,而不是修改URL格式。
需要注意,基于人类可读性的考虑,禁止将 base64 等编码数据嵌入 URL 中。首先, base64 编码不能保证传输安全,其意义在于在 ASCII 信道传输非 ASCII 数据。其次,如果需要保证分享 URL 时的传输安全,请对明文 URL 进行加密,而不是修改 URL 格式。
## 格式
@@ -42,7 +46,7 @@ trojan-go://
例如
```text
trojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Bfuckgfw
trojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Afuckgfw
```
由于 Trojan-Go 兼容 Trojan,所以对于 Trojan 的 URL 方案
@@ -140,7 +144,7 @@ TLS 认证必须开启。无法使用根CA校验服务器身份的节点,不
使用 Shadowsocks 算法进行流量加密时,其格式为:
```text
ss;method;password
ss;method:password
```
其中 ss 是固定内容,method 是加密方法,必须为下列之一:
+80 -42
View File
@@ -1,9 +1,10 @@
package easy
import (
"encoding/json"
"flag"
"fmt"
"net"
"strconv"
"github.com/p4gefau1t/trojan-go/option"
@@ -22,6 +23,31 @@ type easy struct {
key *string
}
type ClientConfig struct {
RunType string `json:"run_type"`
LocalAddr string `json:"local_addr"`
LocalPort int `json:"local_port"`
RemoteAddr string `json:"remote_addr"`
RemotePort int `json:"remote_port"`
Password []string `json:"password"`
}
type TLS struct {
SNI string `json:"sni"`
Cert string `json:"cert"`
Key string `json:"key"`
}
type ServerConfig struct {
RunType string `json:"run_type"`
LocalAddr string `json:"local_addr"`
LocalPort int `json:"local_port"`
RemoteAddr string `json:"remote_addr"`
RemotePort int `json:"remote_port"`
Password []string `json:"password"`
TLS `json:"ssl"`
}
func (o *easy) Name() string {
return "easy"
}
@@ -35,34 +61,41 @@ func (o *easy) Handle() error {
}
log.Info("easy mode enabled, trojan-go will NOT use the config file")
if *o.client {
clientConfigFormat := `
{
"run_type": "client",
"local_addr": "%s",
"local_port": %s,
"remote_addr": "%s",
"remote_port": %s,
"password": [
"%s"
]
}
`
if *o.local == "" {
log.Warn("client local addr is unspecified, using 127.0.0.1:1080")
*o.local = "127.0.0.1:1080"
}
localHost, localPort, err := net.SplitHostPort(*o.local)
localHost, localPortStr, err := net.SplitHostPort(*o.local)
if err != nil {
log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err))
}
remoteHost, remotePort, err := net.SplitHostPort(*o.remote)
remoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)
if err != nil {
log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err))
}
clientConfigJSON := fmt.Sprintf(clientConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password)
log.Info("generated json config:")
log.Info(clientConfigJSON)
proxy, err := proxy.NewProxyFromConfigData([]byte(clientConfigJSON), true)
localPort, err := strconv.Atoi(localPortStr)
if err != nil {
log.Fatal(err)
}
remotePort, err := strconv.Atoi(remotePortStr)
if err != nil {
log.Fatal(err)
}
clientConfig := ClientConfig{
RunType: "client",
LocalAddr: localHost,
LocalPort: localPort,
RemoteAddr: remoteHost,
RemotePort: remotePort,
Password: []string{
*o.password,
},
}
clientConfigJSON, err := json.Marshal(&clientConfig)
common.Must(err)
log.Info("generated config:")
log.Info(string(clientConfigJSON))
proxy, err := proxy.NewProxyFromConfigData(clientConfigJSON, true)
if err != nil {
log.Fatal(err)
}
@@ -71,23 +104,6 @@ func (o *easy) Handle() error {
}
} else if *o.server {
serverConfigFormat := `
{
"run_type": "server",
"local_addr": "%s",
"local_port": %s,
"remote_addr": "%s",
"remote_port": %s,
"password": [
"%s"
],
"ssl": {
"verify_hostname": false,
"cert": "%s",
"key": "%s"
}
}
`
if *o.remote == "" {
log.Warn("server remote addr is unspecified, using 127.0.0.1:80")
*o.remote = "127.0.0.1:80"
@@ -96,19 +112,41 @@ func (o *easy) Handle() error {
log.Warn("server local addr is unspecified, using 0.0.0.0:443")
*o.local = "0.0.0.0:443"
}
localHost, localPort, err := net.SplitHostPort(*o.local)
localHost, localPortStr, err := net.SplitHostPort(*o.local)
if err != nil {
log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err))
}
remoteHost, remotePort, err := net.SplitHostPort(*o.remote)
remoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)
if err != nil {
log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err))
}
serverConfigJSON := fmt.Sprintf(serverConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password, *o.cert, *o.key)
localPort, err := strconv.Atoi(localPortStr)
if err != nil {
log.Fatal(err)
}
remotePort, err := strconv.Atoi(remotePortStr)
if err != nil {
log.Fatal(err)
}
serverConfig := ServerConfig{
RunType: "server",
LocalAddr: localHost,
LocalPort: localPort,
RemoteAddr: remoteHost,
RemotePort: remotePort,
Password: []string{
*o.password,
},
TLS: TLS{
Cert: *o.cert,
Key: *o.key,
},
}
serverConfigJSON, err := json.Marshal(&serverConfig)
common.Must(err)
log.Info("generated json config:")
log.Info(serverConfigJSON)
proxy, err := proxy.NewProxyFromConfigData([]byte(serverConfigJSON), true)
log.Info(string(serverConfigJSON))
proxy, err := proxy.NewProxyFromConfigData(serverConfigJSON, true)
if err != nil {
log.Fatal(err)
}
+4 -4
View File
@@ -74,12 +74,12 @@ func (u *url) Handle() error {
ssMethod := ""
if strings.HasPrefix(info.Encryption, "ss;") {
ssEnabled = true
ssConfig := strings.Split(info.Encryption, ";")
if len(ssConfig) != 3 {
ssConfig := strings.Split(info.Encryption[3:], ":")
if len(ssConfig) != 2 {
log.Fatalf("invalid shadowsocks config: %s", info.Encryption)
}
ssMethod = ssConfig[1]
ssPassword = ssConfig[2]
ssMethod = ssConfig[0]
ssPassword = ssConfig[1]
}
muxEnabled := false
listenHost := "127.0.0.1"
+2 -2
View File
@@ -11,8 +11,8 @@ func TestUrl_Handle(t *testing.T) {
urlCases := []string{
"trojan-go://password@server.com",
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath",
"trojan-go://password@server.com/?encryption=ss%3Baes-256-gcm%3Bfuckgfw",
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Bfuckgfw",
"trojan-go://password@server.com/?encryption=ss%3baes-256-gcm%3afuckgfw",
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Afuckgfw",
}
optionCases := []string{
"mux=true;listen=127.0.0.1:0",
+30 -8
View File
@@ -18,6 +18,10 @@ var validTypes = map[string]struct{}{
ShareInfoTypeWebSocket: {},
}
var validEncryptionProviders = map[string]struct{}{
"ss": {},
}
var validSSEncryptionMap = map[string]struct{}{
"aes-128-gcm": {},
"aes-256-gcm": {},
@@ -145,19 +149,37 @@ func NewShareInfoFromURL(shareLink string) (info ShareInfo, e error) {
e = errors.New("empty encryption")
return
} else {
if strings.HasPrefix(info.Encryption, "ss;") {
if parts := strings.SplitN(info.Encryption, ";", 3); len(parts) != 3 {
e = errors.New("expected 3 ss encryption params")
encryptionParts := strings.SplitN(info.Encryption, ";", 2)
encryptionProviderName := encryptionParts[0]
if _, ok := validEncryptionProviders[encryptionProviderName]; !ok {
e = errors.New(fmt.Sprintf("unsupported encryption provider name: %s", encryptionProviderName))
return
}
var encryptionParams string
if len(encryptionParts) >= 2 {
encryptionParams = encryptionParts[1]
}
if encryptionProviderName == "ss" {
ssParams := strings.SplitN(encryptionParams, ":", 2)
if len(ssParams) < 2 {
e = errors.New("missing ss password")
return
} else if _, ok := validSSEncryptionMap[parts[1]]; !ok {
e = errors.New(fmt.Sprintf("unsupported ss encryption method: %s", parts[1]))
}
ssMethod, ssPassword := ssParams[0], ssParams[1]
if _, ok := validSSEncryptionMap[ssMethod]; !ok {
e = errors.New(fmt.Sprintf("unsupported ss method: %s", ssMethod))
return
} else if parts[2] == "" {
e = errors.New("empty ss encryption key")
}
if ssPassword == "" {
e = errors.New("ss password cannot be empty")
return
}
} else {
// add more encryption support here
e = errors.New(fmt.Sprintf("encryption param %s is not supported", info.Encryption))
return
}
+2 -2
View File
@@ -222,13 +222,13 @@ func TestNewShareInfoFromURL_Encryption_Unknown(t *testing.T) {
func TestNewShareInfoFromURL_Encryption_SS_NotSupportedMethods(t *testing.T) {
invalidMethods := []string{"rc4-md5", "rc4", "des-cfb", "table", "salsa20-ctr"}
for _, invalidMethod := range invalidMethods {
_, e := NewShareInfoFromURL(fmt.Sprintf("trojan-go://a@b.c?encryption=ss%%3B%s%%3Bshabi", invalidMethod))
_, e := NewShareInfoFromURL(fmt.Sprintf("trojan-go://a@b.c?encryption=ss%%3B%s%%3Ashabi", invalidMethod))
assert.Error(t, e, "encryption %s should not be supported by ss", invalidMethod)
}
}
func TestNewShareInfoFromURL_Encryption_SS_NoPassword(t *testing.T) {
_, e := NewShareInfoFromURL("trojan-go://a@b.c?encryption=ss%3Baes-256-gcm%3B")
_, e := NewShareInfoFromURL("trojan-go://a@b.c?encryption=ss%3Baes-256-gcm%3A")
assert.Error(t, e, "empty ss password should not be allowed")
}