|
|
@@ -0,0 +1,169 @@
|
|
|
+//go:build with_ech
|
|
|
+
|
|
|
+package tls
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "encoding/binary"
|
|
|
+ "encoding/pem"
|
|
|
+
|
|
|
+ cftls "github.com/sagernet/cloudflare-tls"
|
|
|
+ E "github.com/sagernet/sing/common/exceptions"
|
|
|
+
|
|
|
+ "github.com/cloudflare/circl/hpke"
|
|
|
+ "github.com/cloudflare/circl/kem"
|
|
|
+)
|
|
|
+
|
|
|
+func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (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},
|
|
|
+ }
|
|
|
+ if pqSignatureSchemesEnabled {
|
|
|
+ keyConfig = append(keyConfig, myECHKeyConfig{id: 1, kem: hpke.KEM_X25519_KYBER768_DRAFT00})
|
|
|
+ }
|
|
|
+
|
|
|
+ keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ var configBuffer bytes.Buffer
|
|
|
+ var totalLen uint16
|
|
|
+ for _, keyPair := range keyPairs {
|
|
|
+ totalLen += uint16(len(keyPair.rawConf))
|
|
|
+ }
|
|
|
+ binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
|
|
+ for _, keyPair := range keyPairs {
|
|
|
+ configBuffer.Write(keyPair.rawConf)
|
|
|
+ }
|
|
|
+
|
|
|
+ var keyBuffer bytes.Buffer
|
|
|
+ for _, keyPair := range keyPairs {
|
|
|
+ keyBuffer.Write(keyPair.rawKey)
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|
|
|
+}
|
|
|
+
|
|
|
+type echKeyConfigPair struct {
|
|
|
+ id uint8
|
|
|
+ key cftls.EXP_ECHKey
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ 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()
|
|
|
+ sk := []byte{}
|
|
|
+ sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
|
|
+ sk = append(sk, secBuf...)
|
|
|
+ sk = be.AppendUint16(sk, uint16(len(b)))
|
|
|
+ sk = append(sk, b...)
|
|
|
+
|
|
|
+ cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
|
|
|
+ if err != nil {
|
|
|
+ return nil, E.Cause(err, "bug: can't parse generated ECH server key")
|
|
|
+ }
|
|
|
+ if len(cfECHKeys) != 1 {
|
|
|
+ return nil, E.New("bug: unexpected server key count")
|
|
|
+ }
|
|
|
+ pair.key = cfECHKeys[0]
|
|
|
+ pair.rawKey = sk
|
|
|
+
|
|
|
+ pairs = append(pairs, pair)
|
|
|
+ }
|
|
|
+ return pairs, nil
|
|
|
+}
|