|
@@ -1,14 +1,11 @@
|
|
|
package tls
|
|
|
|
|
|
import (
|
|
|
- "bytes"
|
|
|
- "encoding/binary"
|
|
|
+ "crypto/ecdh"
|
|
|
+ "crypto/rand"
|
|
|
"encoding/pem"
|
|
|
|
|
|
- E "github.com/sagernet/sing/common/exceptions"
|
|
|
-
|
|
|
- "github.com/cloudflare/circl/hpke"
|
|
|
- "github.com/cloudflare/circl/kem"
|
|
|
+ "golang.org/x/crypto/cryptobyte"
|
|
|
)
|
|
|
|
|
|
type ECHCapableConfig interface {
|
|
@@ -17,145 +14,68 @@ type ECHCapableConfig interface {
|
|
|
SetECHConfigList([]byte)
|
|
|
}
|
|
|
|
|
|
-func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
|
|
- 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)
|
|
|
+func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
|
|
+ echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
|
|
if err != nil {
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- var configBuffer bytes.Buffer
|
|
|
- var totalLen uint16
|
|
|
- for _, keyPair := range keyPairs {
|
|
|
- totalLen += uint16(len(keyPair.rawConf))
|
|
|
+ echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
}
|
|
|
- binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
|
|
- for _, keyPair := range keyPairs {
|
|
|
- configBuffer.Write(keyPair.rawConf)
|
|
|
+ configBuilder := cryptobyte.NewBuilder(nil)
|
|
|
+ configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ builder.AddBytes(echConfig)
|
|
|
+ })
|
|
|
+ configBytes, err := configBuilder.Bytes()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
}
|
|
|
-
|
|
|
- var keyBuffer bytes.Buffer
|
|
|
- for _, keyPair := range keyPairs {
|
|
|
- keyBuffer.Write(keyPair.rawKey)
|
|
|
+ keyBuilder := cryptobyte.NewBuilder(nil)
|
|
|
+ keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ builder.AddBytes(echKey.Bytes())
|
|
|
+ })
|
|
|
+ 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: configBuffer.Bytes()}))
|
|
|
- keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
|
|
+ configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
|
|
+ keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
-type echKeyConfigPair struct {
|
|
|
- id uint8
|
|
|
- rawKey []byte
|
|
|
- conf myECHKeyConfig
|
|
|
- rawConf []byte
|
|
|
-}
|
|
|
-
|
|
|
-type echCipherSuite struct {
|
|
|
- kdf hpke.KDF
|
|
|
- aead hpke.AEAD
|
|
|
-}
|
|
|
-
|
|
|
-type myECHKeyConfig struct {
|
|
|
- id uint8
|
|
|
- kem hpke.KEM
|
|
|
- seed []byte
|
|
|
-}
|
|
|
-
|
|
|
-func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
|
|
- be := binary.BigEndian
|
|
|
- // prepare for future update
|
|
|
- if version != 0xfe0d {
|
|
|
- return nil, E.New("unsupported ECH version", version)
|
|
|
- }
|
|
|
-
|
|
|
- suiteBuf := make([]byte, 0, len(suite)*4+2)
|
|
|
- suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
|
|
- for _, s := range suite {
|
|
|
- if !s.kdf.IsValid() || !s.aead.IsValid() {
|
|
|
- return nil, E.New("invalid HPKE cipher suite")
|
|
|
- }
|
|
|
- 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
|
|
|
+func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
|
|
+ const extensionEncryptedClientHello = 0xfe0d
|
|
|
+ const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
|
|
+ const KDF_HKDF_SHA256 = 0x0001
|
|
|
+ builder := cryptobyte.NewBuilder(nil)
|
|
|
+ builder.AddUint16(extensionEncryptedClientHello)
|
|
|
+ builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ builder.AddUint8(id)
|
|
|
+
|
|
|
+ builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
|
|
+ builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ builder.AddBytes(pubKey)
|
|
|
+ })
|
|
|
+ builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ const (
|
|
|
+ AEAD_AES_128_GCM = 0x0001
|
|
|
+ AEAD_AES_256_GCM = 0x0002
|
|
|
+ AEAD_ChaCha20Poly1305 = 0x0003
|
|
|
+ )
|
|
|
+ for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
|
|
+ builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
|
|
+ builder.AddUint16(aeadID)
|
|
|
}
|
|
|
- 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
|
|
|
+ })
|
|
|
+ builder.AddUint8(maxNameLen)
|
|
|
+ builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
|
+ builder.AddBytes([]byte(publicName))
|
|
|
+ })
|
|
|
+ builder.AddUint16(0) // extensions
|
|
|
+ })
|
|
|
+ return builder.Bytes()
|
|
|
}
|