main
go 124 lines 3.29 KB
Raw
1 package keyless
2
3 import (
4 "bytes"
5 "crypto/ecdh"
6 "crypto/hkdf"
7 "crypto/sha256"
8 "crypto/tls"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "strings"
13
14 "github.com/gosuda/portal-tunnel/v2/utils"
15 )
16
17 const (
18 echConfigVersion = 0xfe0d
19 echKEMX25519 = 0x0020
20 echKDFHKDFSHA256 = 0x0001
21 echAEADAES128GCM = 0x0001
22 echMaximumNameLength = 255
23 echMaxConfigListLength = 4096
24 echX25519PrivateLength = 32
25 echHKDFInfoPrefix = "portal relay ech v1:"
26 )
27
28 // MinTLSVersion returns the minimum TLS version required when ECH is enabled.
29 func MinTLSVersion(echEnabled bool) uint16 {
30 if echEnabled {
31 return tls.VersionTLS13
32 }
33 return tls.VersionTLS12
34 }
35
36 func EncryptedClientHelloMaterials(seed, publicName string) ([]tls.EncryptedClientHelloKey, []byte, error) {
37 publicName = utils.NormalizeHostname(publicName)
38 if publicName == "" {
39 return nil, nil, errors.New("ech public name is required")
40 }
41 seed = strings.TrimSpace(seed)
42 if seed == "" {
43 return nil, nil, errors.New("ech seed is required")
44 }
45
46 if len(publicName) > echMaximumNameLength {
47 return nil, nil, errors.New("ech public name is too long")
48 }
49
50 privateKey, err := hkdf.Key(sha256.New, []byte(seed), nil, echHKDFInfoPrefix+publicName, echX25519PrivateLength)
51 if err != nil {
52 return nil, nil, fmt.Errorf("derive ech private key: %w", err)
53 }
54 key, err := ecdh.X25519().NewPrivateKey(privateKey)
55 if err != nil {
56 return nil, nil, fmt.Errorf("parse ech private key: %w", err)
57 }
58 publicKey := key.PublicKey().Bytes()
59 configID := sha256.Sum256(bytes.Join([][]byte{
60 []byte("portal relay ech config id v1"),
61 []byte(publicName),
62 publicKey,
63 }, []byte{0}))[0]
64
65 writeUint16 := func(buf *bytes.Buffer, value uint16) {
66 var out [2]byte
67 binary.BigEndian.PutUint16(out[:], value)
68 buf.Write(out[:])
69 }
70 writeUint16LengthPrefixed := func(buf *bytes.Buffer, data []byte) {
71 writeUint16(buf, uint16(len(data)))
72 buf.Write(data)
73 }
74
75 var body bytes.Buffer
76 body.WriteByte(configID)
77 writeUint16(&body, echKEMX25519)
78 writeUint16LengthPrefixed(&body, publicKey)
79
80 var cipherSuites bytes.Buffer
81 writeUint16(&cipherSuites, echKDFHKDFSHA256)
82 writeUint16(&cipherSuites, echAEADAES128GCM)
83 writeUint16LengthPrefixed(&body, cipherSuites.Bytes())
84
85 body.WriteByte(echMaximumNameLength)
86 body.WriteByte(byte(len(publicName)))
87 body.WriteString(publicName)
88 writeUint16(&body, 0)
89
90 var out bytes.Buffer
91 writeUint16(&out, echConfigVersion)
92 writeUint16LengthPrefixed(&out, body.Bytes())
93
94 keys := []tls.EncryptedClientHelloKey{{
95 Config: out.Bytes(),
96 PrivateKey: privateKey,
97 SendAsRetry: true,
98 }}
99
100 var configList bytes.Buffer
101 var configListLength [2]byte
102 binary.BigEndian.PutUint16(configListLength[:], uint16(len(keys[0].Config)))
103 configList.Write(configListLength[:])
104 configList.Write(keys[0].Config)
105
106 return keys, configList.Bytes(), nil
107 }
108
109 func NormalizeEncryptedClientHelloConfigList(raw []byte) ([]byte, error) {
110 if len(raw) == 0 {
111 return nil, errors.New("ech config list is required")
112 }
113 if len(raw) > echMaxConfigListLength {
114 return nil, errors.New("ech config list is too large")
115 }
116 if len(raw) < 2 {
117 return nil, errors.New("ech config list is invalid")
118 }
119 listLength := int(binary.BigEndian.Uint16(raw[:2]))
120 if listLength != len(raw)-2 {
121 return nil, errors.New("ech config list length prefix is invalid")
122 }
123 return bytes.Clone(raw), nil
124 }