-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
160 lines (150 loc) · 3.9 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package ech
import (
"crypto/ecdh"
"crypto/rand"
"crypto/tls"
"errors"
"golang.org/x/crypto/cryptobyte"
)
// Config is a serialized ECH Config.
type Config []byte
type Key = tls.EncryptedClientHelloKey
// Config returns a serialized ECH ConfigList.
func ConfigList(configs []Config) ([]byte, error) {
b := cryptobyte.NewBuilder(nil)
b.AddUint16LengthPrefixed(func(c *cryptobyte.Builder) {
for _, cfg := range configs {
c.AddBytes(cfg)
}
})
return b.Bytes()
}
// NewConfig generates an ECH Config and a private key. It currently supports:
// - DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20Poly1305.
// - DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-256-GCM.
// - DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-128-GCM.
func NewConfig(id uint8, publicName []byte) (*ecdh.PrivateKey, Config, error) {
if l := len(publicName); l == 0 || l > 255 {
return nil, nil, errors.New("invalid public name length")
}
privKey, err := ecdh.X25519().GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
c := ConfigSpec{
Version: 0xfe0d,
ID: id,
KEM: 0x0020, // DHKEM(X25519, HKDF-SHA256)
PublicKey: privKey.PublicKey().Bytes(),
CipherSuites: []CipherSuite{
{
KDF: 0x0001, // HKDF-SHA256
AEAD: 0x0003, // ChaCha20Poly1305
},
{
KDF: 0x0001, // HKDF-SHA256
AEAD: 0x0002, // AES-256-GCM
},
{
KDF: 0x0001, // HKDF-SHA256
AEAD: 0x0001, // AES-128-GCM
},
},
MinimumNameLength: uint8(min(len(publicName)+16, 255)),
PublicName: publicName,
}
conf, err := c.Bytes()
if err != nil {
return nil, nil, err
}
return privKey, conf, nil
}
// Spec returns the structured version of cfg.
func (cfg Config) Spec() (ConfigSpec, error) {
var out ConfigSpec
s := cryptobyte.String(cfg)
if !s.ReadUint16(&out.Version) {
return out, ErrDecodeError
}
var ss cryptobyte.String
if !s.ReadUint16LengthPrefixed(&ss) {
return out, ErrDecodeError
}
s = ss
if !s.ReadUint8(&out.ID) {
return out, ErrDecodeError
}
if !s.ReadUint16(&out.KEM) {
return out, ErrDecodeError
}
if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&out.PublicKey)) {
return out, ErrDecodeError
}
var cs cryptobyte.String
if !s.ReadUint16LengthPrefixed(&cs) {
return out, ErrDecodeError
}
for !cs.Empty() {
var suite CipherSuite
if !cs.ReadUint16(&suite.KDF) {
return out, ErrDecodeError
}
if !cs.ReadUint16(&suite.AEAD) {
return out, ErrDecodeError
}
out.CipherSuites = append(out.CipherSuites, suite)
}
if !s.ReadUint8(&out.MinimumNameLength) {
return out, ErrDecodeError
}
if !s.ReadUint8LengthPrefixed((*cryptobyte.String)(&out.PublicName)) {
return out, ErrDecodeError
}
return out, nil
}
// ConfigSpec represents an ECH Config. It is specified in Section 4 of
// https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni/
type ConfigSpec struct {
Version uint16
ID uint8
KEM uint16
PublicKey []byte
CipherSuites []CipherSuite
MinimumNameLength uint8
PublicName []byte
}
type CipherSuite struct {
KDF uint16
AEAD uint16
}
// Bytes returns the serialized version of the ECH Config.
func (c ConfigSpec) Bytes() (Config, error) {
if l := len(c.PublicName); l == 0 || l > 255 {
return nil, errors.New("invalid public name length")
}
b := cryptobyte.NewBuilder(nil)
b.AddUint16(c.Version)
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddUint8(c.ID)
b.AddUint16(c.KEM)
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(c.PublicKey)
})
b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
for _, cs := range c.CipherSuites {
b.AddUint16(cs.KDF)
b.AddUint16(cs.AEAD)
}
})
b.AddUint8(uint8(min(len(c.PublicName)+16, 255)))
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
b.AddBytes(c.PublicName)
})
b.AddUint16(0) // extensions
})
conf, err := b.Bytes()
if err != nil {
return nil, err
}
return conf, nil
}