修订代码,文档,示例;支持客户端证书和CA.

This commit is contained in:
e1732a364fed
2022-05-08 08:30:59 +08:00
parent 2371824b30
commit 5723c73297
14 changed files with 255 additions and 60 deletions

8
.gitignore vendored
View File

@@ -14,4 +14,10 @@ geosite
vs_log*
cert.pem
cert.key
BUILD_VERSION
ca.key
ca.crt
BUILD_VERSION
client.crt
client.csr
client.key
.vscode

View File

@@ -225,6 +225,9 @@ func mainFunc() (result int) {
ce.Write(zap.String("dir", wdir))
}
}
if ce := utils.CanLogDebug("All Given Flags"); ce != nil {
ce.Write(zap.Any("flags", utils.GivenFlagKVs()))
}
if loadConfigErr != nil && !isFlexible() {

View File

@@ -73,6 +73,10 @@ utls = true #是否使用 utls 来应用 chrome指纹进行伪装
# 如果要使用 websocket/grpc ,则 客户端和服务端 都要配置 advancedLayer 和 path
# 下面cert和key用于 "客户端证书", 小白可以无视,高级玩家可以用, 详见 vlesss.server.toml
#cert = "client.crt"
#key = "client.key"
# advancedLayer = "ws" # 也可为 grpc 或 quic
# path = "/ohmygod_verysimple_is_very_simple" # ws的path和 grpc的serviceName 都在这个path里填写, 为了防探测这里越长越随机越好

View File

@@ -9,13 +9,25 @@ version = 0
insecure = true
fallback = ":80" # 默认回落地址.ip必须是本机ip(可以省略ip而只写端口,程序会默认补全127.0.0.1), 或者unix domain socket的文件名/路径, 或者 udp://127.0.0.1:80 这种url格式。 用udp以试图回落到 nginx的 无tls 的 udp的 http3 服务端口,适用于 quic的情况
#cert = "cert.pem" # 这里既可以默认放在程序本身目录下,也可以指定完整路径
#key = "cert.key" # 如果 cert和key中 有一项没给出, 或者文件不存在, 就会自动在内存中生成随机证书,
cert = "cert.pem" # 这里既可以默认放在程序本身目录下,也可以指定完整路径
key = "cert.key" # 如果 cert和key中 有一项没给出, 或者文件不存在, 就会自动在内存中生成随机证书,
# 我们作为示例, 就直接随机证书了, 避免很多小白 共同使用相同的证书 导致被 审查者 察觉.
#xver = 1 # 可选, 高级用法, 小白不用管. 若为1或者2, 则监听 PROXY protocol, 用于nginx等回落到 verysimple. 实际上目前无论给出的是1还是2, 都会同时监听 v1和v2. 不过这只是目前代码的实现而已, 也许未来会改动, 所以你还是确定选用一个版本.
# ca = "ca.crt" # 用于验证客户端证书
# 生成ca的命令:
# openssl ecparam -genkey -name prime256v1 -out ca.key
# openssl req -new -x509 -days 365 -sha256 -key ca.key -out ca.crt #会提示让你输入 CountryName 等信息。
# 用ca生成客户端key和crt
# openssl ecparam -genkey -name prime256v1 -out client.key
# openssl req -new -key client.key -out client.csr #会提示 让你输入 CountryName 等信息。
# openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
# 之后 client.key 和 client.crt 就是我们要的 客户端证书。 注意 上面的openssl 生成 crt 的两个命令 要使用 -sha256参数, 因为默认的sha1已经不安全, 在go1.18中被废弃了。
[[dial]]
protocol = "direct"

View File

@@ -14,7 +14,15 @@ const (
// real nginx response, curl -iv --raw 127.0.0.1/not_exist_path > response
Err404response_nginx = "HTTP/1.1 404 Not Found\r\nServer: nginx/1.21.5\r\nDate: Sat, 02 Jan 2006 15:04:05 MST\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 19\r\nConnection: keep-alive\r\nCache-Control: no-cache, no-store, no-transform, must-revalidate, private, max-age=0\r\nExpires: Thu, 01 Jan 1970 08:00:00 AWST\r\nPragma: no-cache\r\nVary: Origin\r\nX-Content-Type-Options: nosniff\r\n\r\n404 page not found"
/* real nginx response, set nginx config like:
location / {
return 403;
}
*/
Err403response_nginx = `HTTP/1.1 403 Forbidden\r\nServer: nginx/1.14.2\r\nDate: Sat, 07 May 2022 07:03:47 GMT\r\nContent-Type: text/html\r\nContent-Length: 169\r\nConnection: keep-alive\r\n\r\n<html>\r\n<head><title>403 Forbidden</title></head>\r\n<body bgcolor="white">\r\n<center><h1>403 Forbidden</h1></center>\r\n<hr><center>nginx/1.14.2</center>\r\n</body>\r\n</html>\r\n`
//备注vim中 "\r" 显示为 ^M, 输入它是用 ctrl + V + M
)
var nginxTimezone = time.FixedZone("GMT", 0)

View File

@@ -39,9 +39,9 @@ func LoadMaxmindGeoipFile(fn string) {
if fn == "" { //因为 GeoipFileName 是公有变量,所以可能会被设成""
return
}
bs, e := os.ReadFile(fn)
bs, e := os.ReadFile(utils.GetFilePath(fn))
if e != nil {
log.Println("LoadMaxmindGeoipFile err", e)
log.Printf("LoadMaxmindGeoipFile err, %s\n", e.Error())
return
}
loadMaxmindGeoipBytes(bs)

View File

@@ -318,20 +318,42 @@ func (b *Base) InitAdvLayer() {
Headers = dc.HttpHeader
}
advClient, err := creator.NewClientFromConf(&advLayer.Conf{
var tConf *tls.Config
if creator.IsSuper() {
tConf = &tls.Config{
InsecureSkipVerify: dc.Insecure,
NextProtos: dc.Alpn,
ServerName: dc.Host,
}
if dc.TLSCert != "" && dc.TLSKey != "" {
certArray, err := tlsLayer.GetCertArrayFromFile(dc.TLSCert, dc.TLSKey)
if err != nil {
if ce := utils.CanLogErr("load client cert file failed"); ce != nil {
ce.Write(zap.Error(err))
}
} else {
tConf.Certificates = certArray
}
}
}
aConf := &advLayer.Conf{
Path: dc.Path,
Host: dc.Host,
IsEarly: dc.IsEarly,
Addr: ad,
Headers: Headers,
Xver: dc.Xver,
TlsConf: &tls.Config{
InsecureSkipVerify: dc.Insecure,
NextProtos: dc.Alpn,
ServerName: dc.Host,
},
Extra: dc.Extra,
})
TlsConf: tConf,
Extra: dc.Extra,
}
advClient, err := creator.NewClientFromConf(aConf)
if err != nil {
if ce := utils.CanLogErr("InitAdvLayer client failed "); ce != nil {
@@ -357,18 +379,38 @@ func (b *Base) InitAdvLayer() {
var certArray []tls.Certificate
if lc.TLSCert != "" && lc.TLSKey != "" {
certArray, err = tlsLayer.GetCertArrayFromFile(lc.TLSCert, lc.TLSKey)
if creator.IsSuper() {
if lc.TLSCert != "" && lc.TLSKey != "" {
certArray, err = tlsLayer.GetCertArrayFromFile(lc.TLSCert, lc.TLSKey)
if err != nil {
if err != nil {
if ce := utils.CanLogErr("can't create tls cert"); ce != nil {
ce.Write(zap.String("cert", lc.TLSCert), zap.String("key", lc.TLSKey), zap.Error(err))
if ce := utils.CanLogErr("can't create tls cert"); ce != nil {
ce.Write(zap.String("cert", lc.TLSCert), zap.String("key", lc.TLSKey), zap.Error(err))
}
return
}
return
}
}
tConf := &tls.Config{
InsecureSkipVerify: lc.Insecure,
NextProtos: lc.Alpn,
ServerName: lc.Host,
Certificates: certArray,
}
if lc.CA != "" {
certPool, err := tlsLayer.LoadCA(lc.CA)
if err != nil {
if ce := utils.CanLogErr("load CA failed"); ce != nil {
ce.Write(zap.Error(err))
}
} else {
tConf.ClientCAs = certPool
tConf.ClientAuth = tls.RequireAndVerifyClientCert
}
}
advSer, err := creator.NewServerFromConf(&advLayer.Conf{
@@ -378,13 +420,8 @@ func (b *Base) InitAdvLayer() {
Xver: lc.Xver,
Addr: ad,
Headers: Headers,
TlsConf: &tls.Config{
InsecureSkipVerify: lc.Insecure,
NextProtos: lc.Alpn,
ServerName: lc.Host,
Certificates: certArray,
},
Extra: lc.Extra,
TlsConf: tConf,
Extra: lc.Extra,
})
if err != nil {

View File

@@ -9,32 +9,51 @@ import (
// CommonConf is the common part of ListenConf and DialConf.
type CommonConf struct {
Tag string `toml:"tag"` //可选
Protocol string `toml:"protocol"` //代理层; 约定如果一个Protocol尾缀去掉了's'后仍然是一个有效协议,则该协议使用了 tls。这种方法继承自 v2simple适合极简模式
Uuid string `toml:"uuid"` //代理层用户的唯一标识视代理层协议而定一般使用uuid但trojan协议是随便的.
Host string `toml:"host"` //ip 或域名. 若unix domain socket 则为文件路径
IP string `toml:"ip"` //给出Host后该项可以省略; 既有Host又有ip的情况比较适合cdn
Port int `toml:"port"` //若Network不为 unix , 则port项必填
Version int `toml:"version"` //可选
Tag string `toml:"tag"` //可选
Extra map[string]any `toml:"extra"` //用于包含任意其它数据.虽然本包自己定义的协议肯定都是已知的,但是如果其他人使用了本包的话,那就有可能添加一些 新协议 特定的数据.
/////////////////// 网络层 ///////////////////
Host string `toml:"host"` //ip 或域名. 若unix domain socket 则为文件路径
IP string `toml:"ip"` //给出Host后该项可以省略; 既有Host又有ip的情况比较适合cdn
/////////////////// 传输层 ///////////////////
Network string `toml:"network"` //传输层协议; 默认使用tcp, network可选值为 tcp, udp, unix; 理论上来说应该用 transportLayer但是怕小白不懂所以使用 network作为名称。而且也不算错因为go的net包 也是用 network来指示 传输层/网络层协议的. 比如 net.Listen()第一个参数可以用 ip, tcp, udp 等。
Sockopt *netLayer.Sockopt `toml:"sockopt"` //可选
Xver int `toml:"xver"` //可选只能为0/1/2. 若不为0, 则使用 PROXY protocol 协议头.
Port int `toml:"port"` //若Network不为 unix , 则port项必填
Xver int `toml:"xver"` //可选只能为0/1/2. 若不为0, 则使用 PROXY protocol 协议头.
/////////////////// tls层 ///////////////////
TLS bool `toml:"tls"` //tls层; 可选. 如果不使用 's' 后缀法则还可以配置这一项来更清晰第标明使用tls
Insecure bool `toml:"insecure"` //tls 是否安全
Alpn []string `toml:"alpn"`
TLSCert string `toml:"cert"` //可选
TLSKey string `toml:"key"` //可选
/////////////////// http层 ///////////////////
HttpHeader *httpLayer.HeaderPreset `toml:"header"` //http伪装头; 可选
AdvancedLayer string `toml:"advancedLayer"` //高级层; 可不填
IsEarly bool `toml:"early"` //是否启用 0-rtt
Path string `toml:"path"` //ws 的path 或 grpc的 serviceName。为了简便我们在同一位置给出.
Extra map[string]any `toml:"extra"` //用于包含任意其它数据.虽然本包自己定义的协议肯定都是已知的,但是如果其他人使用了本包的话,那就有可能添加一些 新协议 特定的数据.
/////////////////// 高级层 ///////////////////
AdvancedLayer string `toml:"advancedLayer"` //高级层; 可选
IsEarly bool `toml:"early"` //是否启用 0-rtt
/////////////////// 代理层 ///////////////////
Protocol string `toml:"protocol"` //代理层; 约定如果一个Protocol尾缀去掉了's'后仍然是一个有效协议,则该协议使用了 tls。这种方法继承自 v2simple适合极简模式
Uuid string `toml:"uuid"` //代理层用户的唯一标识视代理层协议而定一般使用uuid但trojan协议是随便的.
Version int `toml:"version"` //可选代理层协议版本号vless v1 要用到。
}
func (cc *CommonConf) GetAddrStr() string {
@@ -78,9 +97,10 @@ func (cc *CommonConf) GetAddrStrForListenOrDial() string {
// CommonConf.Host , CommonConf.IP, CommonConf.Port is the addr and port for listening
type ListenConf struct {
CommonConf
Fallback any `toml:"fallback"` //可选,默认回落的地址,一般可为 ip:port,数字port or unix socket的文件名
TLSCert string `toml:"cert"`
TLSKey string `toml:"key"`
CA string `toml:"ca"` //可选,用于 验证"客户端证书"
Fallback any `toml:"fallback"` //可选,默认回落的地址,一般可为 ip:port,数字port or unix socket的文件名
//noroute 意味着 传入的数据 不会被分流,一定会被转发到默认的 dial
// 这一项是针对 分流功能的. 如果不设noroute, 则所有listen 得到的流量都会被 试图 进行分流

View File

@@ -155,7 +155,7 @@ Comparison
与本作架构对立的架构是 支持转发链 或者说 “链式转发” 的架构如gost。
实际上 v2ray的架构 更接近 gost 这种架构本作的架构比较独特。
实际上 v2ray的架构 更接近 gost 这种架构本作的架构比较独特。
目前本作架构基本上无法实现链式转发,如果要支持,要进行一些重构。应该是可以做到的。
*/

View File

@@ -55,7 +55,15 @@ func prepareTLS_forClient(com BaseInterface, dc *DialConf) error {
return nil
}
clic.Tls_c = tlsLayer.NewClient(dc.Host, dc.Insecure, dc.Utls, alpnList)
var certConf *tlsLayer.CertConf
if dc.TLSCert != "" && dc.TLSKey != "" {
certConf = &tlsLayer.CertConf{
CertFile: dc.TLSCert,
KeyFile: dc.TLSKey,
}
}
clic.Tls_c = tlsLayer.NewClient(dc.Host, dc.Insecure, dc.Utls, alpnList, certConf)
return nil
}
@@ -69,7 +77,10 @@ func prepareTLS_forServer(com BaseInterface, lc *ListenConf) error {
alpnList := updateAlpnListByAdvLayer(com, lc.Alpn)
tlsserver, err := tlsLayer.NewServer(lc.Host, lc.TLSCert, lc.TLSKey, lc.Insecure, alpnList)
tlsserver, err := tlsLayer.NewServer(lc.Host, &tlsLayer.CertConf{
CertFile: lc.TLSCert, KeyFile: lc.TLSKey, CA: lc.CA,
}, lc.Insecure, alpnList)
if err == nil {
serc.Tls_s = tlsserver
} else {
@@ -93,7 +104,7 @@ func prepareTLS_forProxyCommon_withURL(u *url.URL, isclient bool, com BaseInterf
useUtls := utlsStr != "" && utlsStr != "false" && utlsStr != "0"
if cc != nil {
cc.Tls_c = tlsLayer.NewClient(u.Host, insecure, useUtls, nil)
cc.Tls_c = tlsLayer.NewClient(u.Host, insecure, useUtls, nil, nil)
}
@@ -104,7 +115,9 @@ func prepareTLS_forProxyCommon_withURL(u *url.URL, isclient bool, com BaseInterf
hostAndPort := u.Host
sni, _, _ := net.SplitHostPort(hostAndPort)
tlsserver, err := tlsLayer.NewServer(sni, certFile, keyFile, insecure, nil)
tlsserver, err := tlsLayer.NewServer(sni, &tlsLayer.CertConf{
CertFile: certFile, KeyFile: keyFile,
}, insecure, nil)
if err == nil {
if cc != nil {
cc.Tls_s = tlsserver

View File

@@ -1,6 +1,8 @@
package tlsLayer
import (
"errors"
"io/ioutil"
mathrand "math/rand"
"crypto/ecdsa"
@@ -19,6 +21,29 @@ import (
"go.uber.org/zap"
)
var ErrCAFileWrong = errors.New("ca file is somehow wrong")
type CertConf struct {
CA string
CertFile, KeyFile string
}
func LoadCA(caFile string) (cp *x509.CertPool, err error) {
if caFile == "" {
err = utils.ErrNilParameter
return
}
cp = x509.NewCertPool()
data, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
}
if !cp.AppendCertsFromPEM(data) {
return nil, ErrCAFileWrong
}
return
}
//使用 ecc p256 方式生成证书
func GenerateRandomeCert_Key() (certPEM []byte, keyPEM []byte) {

View File

@@ -20,7 +20,7 @@ type Client struct {
alpnList []string
}
func NewClient(host string, insecure bool, use_uTls bool, alpnList []string) *Client {
func NewClient(host string, insecure bool, use_uTls bool, alpnList []string, certConf *CertConf) *Client {
c := &Client{
use_uTls: use_uTls,
@@ -29,21 +29,56 @@ func NewClient(host string, insecure bool, use_uTls bool, alpnList []string) *Cl
c.alpnList = alpnList
if use_uTls {
c.uTlsConfig = utls.Config{
tConf := utls.Config{
InsecureSkipVerify: insecure,
ServerName: host,
NextProtos: alpnList,
}
if certConf != nil {
certArray, err := GetCertArrayFromFile(certConf.CertFile, certConf.KeyFile)
if err != nil {
if ce := utils.CanLogErr("load client cert file failed"); ce != nil {
ce.Write(zap.Error(err))
}
} else {
var array []utls.Certificate
for _, c := range certArray {
array = append(array, *(*utls.Certificate)(unsafe.Pointer(&c)))
}
tConf.Certificates = array
}
}
c.uTlsConfig = tConf
if ce := utils.CanLogInfo("using utls and Chrome fingerprint for"); ce != nil {
ce.Write(zap.String("host", host))
}
} else {
c.tlsConfig = &tls.Config{
tConf := &tls.Config{
InsecureSkipVerify: insecure,
ServerName: host,
NextProtos: alpnList,
}
if certConf != nil {
certArray, err := GetCertArrayFromFile(certConf.CertFile, certConf.KeyFile)
if err != nil {
if ce := utils.CanLogErr("load client cert file failed"); ce != nil {
ce.Write(zap.Error(err))
}
} else {
tConf.Certificates = certArray
}
}
c.tlsConfig = tConf
}

View File

@@ -6,6 +6,7 @@ import (
"unsafe"
"github.com/e1732a364fed/v2ray_simple/utils"
"go.uber.org/zap"
"golang.org/x/exp/slices"
)
@@ -14,12 +15,17 @@ type Server struct {
}
//如 certFile, keyFile 有一项没给出,则会自动生成随机证书
func NewServer(host, certFile, keyFile string, isInsecure bool, alpnList []string) (*Server, error) {
func NewServer(host string, certConf *CertConf, isInsecure bool, alpnList []string) (*Server, error) {
certArray, err := GetCertArrayFromFile(certFile, keyFile)
var certArray []tls.Certificate
var err error
if err != nil {
return nil, err
if certConf != nil {
certArray, err = GetCertArrayFromFile(certConf.CertFile, certConf.KeyFile)
if err != nil {
return nil, err
}
}
//发现服务端必须给出 http/1.1 等否则不会协商出这个alpn而我们为了回落是需要协商出所有可能需要的 alpn的。
@@ -38,13 +44,29 @@ func NewServer(host, certFile, keyFile string, isInsecure bool, alpnList []strin
}
}
tConf := &tls.Config{
InsecureSkipVerify: isInsecure,
ServerName: host,
Certificates: certArray,
NextProtos: alpnList,
}
if certConf != nil {
if certConf.CA != "" {
certPool, err := LoadCA(certConf.CA)
if err != nil {
if ce := utils.CanLogErr("load CA failed"); ce != nil {
ce.Write(zap.Error(err))
}
} else {
tConf.ClientCAs = certPool
tConf.ClientAuth = tls.RequireAndVerifyClientCert
}
}
}
s := &Server{
tlsConfig: &tls.Config{
InsecureSkipVerify: isInsecure,
ServerName: host,
Certificates: certArray,
NextProtos: alpnList,
},
tlsConfig: tConf,
}
return s, nil

View File

@@ -46,6 +46,16 @@ func ParseFlags() {
GivenFlags = GetGivenFlags()
}
//return kv pairs for GivenFlags
func GivenFlagKVs() (r map[string]string) {
r = map[string]string{}
for k, f := range GivenFlags {
r[k] = f.Value.String()
}
return
}
//移除 = "" 和 = false 的项
func GetPurgedTomlStr(v any) (string, error) {
buf := GetBuf()