mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-07-26 16:04:08 +08:00
Compare commits
6 Commits
082572433e
...
738223b2a2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
738223b2a2 | ||
![]() |
685d7afd9b | ||
![]() |
1e068c78e6 | ||
![]() |
54be54c3b9 | ||
![]() |
9cdfe8c722 | ||
![]() |
4931f12835 |
@ -1,11 +1,14 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdh"
|
"bytes"
|
||||||
"crypto/rand"
|
"encoding/binary"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
type ECHCapableConfig interface {
|
||||||
@ -14,68 +17,145 @@ type ECHCapableConfig interface {
|
|||||||
SetECHConfigList([]byte)
|
SetECHConfigList([]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
||||||
if err != nil {
|
var configBuffer bytes.Buffer
|
||||||
return
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
}
|
}
|
||||||
configBuilder := cryptobyte.NewBuilder(nil)
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
for _, keyPair := range keyPairs {
|
||||||
builder.AddBytes(echConfig)
|
configBuffer.Write(keyPair.rawConf)
|
||||||
})
|
|
||||||
configBytes, err := configBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
keyBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
var keyBuffer bytes.Buffer
|
||||||
builder.AddBytes(echKey.Bytes())
|
for _, keyPair := range keyPairs {
|
||||||
})
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
keyBytes, err := keyBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
|
||||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
type echKeyConfigPair struct {
|
||||||
const extensionEncryptedClientHello = 0xfe0d
|
id uint8
|
||||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
rawKey []byte
|
||||||
const KDF_HKDF_SHA256 = 0x0001
|
conf myECHKeyConfig
|
||||||
builder := cryptobyte.NewBuilder(nil)
|
rawConf []byte
|
||||||
builder.AddUint16(extensionEncryptedClientHello)
|
}
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddUint8(id)
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
aead hpke.AEAD
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
}
|
||||||
builder.AddBytes(pubKey)
|
|
||||||
})
|
type myECHKeyConfig struct {
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
id uint8
|
||||||
const (
|
kem hpke.KEM
|
||||||
AEAD_AES_128_GCM = 0x0001
|
seed []byte
|
||||||
AEAD_AES_256_GCM = 0x0002
|
}
|
||||||
AEAD_ChaCha20Poly1305 = 0x0003
|
|
||||||
)
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
be := binary.BigEndian
|
||||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
// prepare for future update
|
||||||
builder.AddUint16(aeadID)
|
if version != 0xfe0d {
|
||||||
}
|
return nil, E.New("unsupported ECH version", version)
|
||||||
})
|
}
|
||||||
builder.AddUint8(maxNameLen)
|
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
builder.AddBytes([]byte(publicName))
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
})
|
for _, s := range suite {
|
||||||
builder.AddUint16(0) // extensions
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
})
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
return builder.Bytes()
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
pair.rawKey = sk
|
||||||
|
|
||||||
|
pairs = append(pairs, pair)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-rc.2
|
#### 1.12.0-rc.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.23.1
|
|||||||
require (
|
require (
|
||||||
github.com/anytls/sing-anytls v0.0.8
|
github.com/anytls/sing-anytls v0.0.8
|
||||||
github.com/caddyserver/certmagic v0.23.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
|
github.com/cloudflare/circl v1.6.1
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
|
2
go.sum
2
go.sum
@ -20,6 +20,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
|
Loading…
x
Reference in New Issue
Block a user