diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 6b55b2f9..484693ed 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -414,6 +414,7 @@ type TLSConfig struct { VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` ECHConfig string `json:"echConfig"` ECHDOHServer string `json:"echDohServer"` + EchKeySets string `json:"echKeySets"` } // Build implements Buildable. @@ -492,6 +493,13 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.EchConfig = ECHConfig } + if c.EchKeySets != "" { + EchPrivateKey, err := base64.StdEncoding.DecodeString(c.EchKeySets) + if err != nil { + return nil, errors.New("invalid ECH Config", c.EchKeySets) + } + config.EchKeySets = EchPrivateKey + } config.Ech_DOHserver = c.ECHDOHServer return config, nil diff --git a/main/commands/all/tls/ech.go b/main/commands/all/tls/ech.go index 184f20f4..c8aa2953 100644 --- a/main/commands/all/tls/ech.go +++ b/main/commands/all/tls/ech.go @@ -43,8 +43,13 @@ func executeECH(cmd *base.Command, args []string) { echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem, nil) common.Must(err) - configBuffer, _ := echKeySet.ECHConfig.MarshalBinary() - keyBuffer, _ := echKeySet.MarshalBinary() + // Make single key set to a list with only one element + ECHConfigList := make(goech.ECHConfigList, 1) + ECHConfigList[0] = echKeySet.ECHConfig + ECHKeySetList := make(goech.ECHKeySetList, 1) + ECHKeySetList[0] = echKeySet + configBuffer, _ := ECHConfigList.MarshalBinary() + keyBuffer, _ := ECHKeySetList.MarshalBinary() configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer})) keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer})) diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 0ab889ac..0193f931 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -444,7 +444,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config.KeyLogWriter = writer } } - if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 { + if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 || len(c.EchKeySets) > 0 { err := ApplyECH(c, config) if err != nil { errors.LogError(context.Background(), err) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index e4d43648..731931df 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -217,8 +217,9 @@ type Config struct { // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. // @Critical VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` - EchConfig []byte `protobuf:"bytes,20,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` - Ech_DOHserver string `protobuf:"bytes,21,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` + EchConfig []byte `protobuf:"bytes,18,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` + Ech_DOHserver string `protobuf:"bytes,19,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` + EchKeySets []byte `protobuf:"bytes,20,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"` } func (x *Config) Reset() { @@ -377,6 +378,13 @@ func (x *Config) GetEch_DOHserver() string { return "" } +func (x *Config) GetEchKeySets() []byte { + if x != nil { + return x.EchKeySets + } + return nil +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -408,7 +416,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, - 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xde, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x80, 0x07, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, @@ -459,18 +467,20 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, + 0x66, 0x69, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4f, 0x48, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, - 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, - 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, - 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, - 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, + 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x63, 0x68, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, + 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, + 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, + 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index b10f8cd3..c1b7a026 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -92,9 +92,9 @@ message Config { */ repeated string verify_peer_cert_in_names = 17; + bytes ech_config = 18; + string ech_DOHserver = 19; - bytes ech_config = 20; - - string ech_DOHserver = 21; + bytes ech_key_sets = 20; } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index a5a6a305..572db5b3 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -4,12 +4,14 @@ import ( "bytes" "context" "crypto/tls" + "fmt" "io" "net/http" "strings" "sync" "time" + "github.com/OmarTariq612/goech" "github.com/miekg/dns" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" @@ -23,31 +25,53 @@ func ApplyECH(c *Config, config *tls.Config) error { nameToQuery := c.ServerName var DOHServer string - parts := strings.Split(c.Ech_DOHserver, "+") - if len(parts) == 2 { - // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" - nameToQuery = parts[0] - DOHServer = parts[1] - } else if len(parts) == 1 { - // normal format - DOHServer = parts[0] - } else { - return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + if len(c.EchConfig) != 0 { + parts := strings.Split(c.Ech_DOHserver, "+") + if len(parts) == 2 { + // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" + nameToQuery = parts[0] + DOHServer = parts[1] + } else if len(parts) == 1 { + // normal format + DOHServer = parts[0] + } else { + return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + } + + if len(c.EchConfig) > 0 { + ECHConfig = c.EchConfig + } else { // ECH config > DOH lookup + if nameToQuery == "" { + return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") + } + ECHConfig, err = QueryRecord(nameToQuery, DOHServer) + if err != nil { + return err + } + } + + config.EncryptedClientHelloConfigList = ECHConfig } - if len(c.EchConfig) > 0 { - ECHConfig = c.EchConfig - } else { // ECH config > DOH lookup - if nameToQuery == "" { - return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") - } - ECHConfig, err = QueryRecord(nameToQuery, DOHServer) + if len(c.EchKeySets) != 0 { + var keys []tls.EncryptedClientHelloKey + KeySets, err := goech.UnmarshalECHKeySetList(c.EchKeySets) if err != nil { - return err + return errors.New("Failed to unmarshal ECHKeySetList: ", err) } + for idx, keySet := range KeySets { + ECHConfig, err := keySet.ECHConfig.MarshalBinary() + ECHPrivateKey, err := keySet.PrivateKey.MarshalBinary() + if err != nil { + return errors.New("Failed to marshal ECHKey in index: ", idx, "with err: ", err) + } + keys = append(keys, tls.EncryptedClientHelloKey{ + Config: ECHConfig, + PrivateKey: ECHPrivateKey}) + } + config.EncryptedClientHelloKeys = keys + fmt.Println(config.EncryptedClientHelloKeys) } - - config.EncryptedClientHelloConfigList = ECHConfig return nil }