ALL

For reading and verification only. You may not copy, use, modify or redistribute this code. Full notice on the main page.

tunnel/sni.go

SHA-256: 5de67027f4530cabccdf586fc1034a9db03cbe2e19e074be2d5cdd24fabaea7a
278 lines · 7529 bytes · View on GitHub

1package main
2
3import (
4 "errors"
5 "net"
6 "strings"
7 "time"
8)
9
10// This file reads the CLEARTEXT part of a TLS ClientHello, only to learn the
11// server name (SNI) the client asked for. Nothing in this program ever
12// decrypts, terminates or alters a client's TLS session: the bytes read here
13// are replayed verbatim to the device, which is the real TLS server.
14
15const (
16 recordTypeHandshake = 22
17 handshakeClientHello = 1
18
19 maxRecordPayload = 1 << 14 // TLS plaintext record limit
20 maxClientHelloBytes = 64 << 10
21 maxHelloRecords = 32
22 // Upper bound on the bytes buffered while looking for the SNI.
23 maxPeek = maxClientHelloBytes + maxHelloRecords*5 + 4
24)
25
26var (
27 errNotTLS = errors.New("not a TLS handshake record")
28 errNotClientHello = errors.New("handshake is not a ClientHello")
29 errHelloMalformed = errors.New("malformed ClientHello")
30 errHelloTooLarge = errors.New("ClientHello too large")
31 errBadSNI = errors.New("invalid server name")
32)
33
34// helloScan is the outcome of looking at a prefix of the client's bytes.
35type helloScan struct {
36 complete bool // the whole ClientHello message is present
37 sni string // normalized server name; "" when the extension is absent
38 need int // when !complete: total buffer length required before rescanning is useful
39 err error
40 hello []byte // when complete: the ClientHello's body (the observer reads the groups it offers from it)
41}
42
43// scanClientHello inspects buf, the bytes received so far from a client. It
44// never panics on any input and never reads outside buf. The handshake message
45// may be split across several TLS records (rare, but legal) and across any
46// number of TCP reads.
47func scanClientHello(buf []byte) helloScan {
48 if len(buf) == 0 {
49 return helloScan{need: 1}
50 }
51 var hs []byte
52 off := 0
53 for rec := 0; ; rec++ {
54 if rec >= maxHelloRecords {
55 return helloScan{err: errHelloMalformed}
56 }
57 if len(buf)-off >= 1 && buf[off] != recordTypeHandshake {
58 return helloScan{err: errNotTLS}
59 }
60 if len(buf)-off >= 2 && buf[off+1] != 3 {
61 return helloScan{err: errNotTLS}
62 }
63 if len(buf)-off < 5 {
64 return helloScan{need: off + 5}
65 }
66 n := int(buf[off+3])<<8 | int(buf[off+4])
67 if n == 0 || n > maxRecordPayload {
68 return helloScan{err: errHelloMalformed}
69 }
70 if len(buf)-off-5 < n {
71 return helloScan{need: off + 5 + n}
72 }
73 hs = append(hs, buf[off+5:off+5+n]...)
74 off += 5 + n
75
76 if len(hs) >= 1 && hs[0] != handshakeClientHello {
77 return helloScan{err: errNotClientHello}
78 }
79 if len(hs) >= 4 {
80 l := int(hs[1])<<16 | int(hs[2])<<8 | int(hs[3])
81 if l > maxClientHelloBytes {
82 return helloScan{err: errHelloTooLarge}
83 }
84 if len(hs) >= 4+l {
85 sni, err := parseClientHelloBody(hs[4 : 4+l])
86 return helloScan{complete: true, sni: sni, err: err, hello: hs[4 : 4+l]}
87 }
88 }
89 }
90}
91
92// cursor is a bounds-checked reader over a byte slice.
93type cursor struct {
94 b []byte
95 bad bool
96}
97
98func (c *cursor) take(n int) []byte {
99 if c.bad || n < 0 || n > len(c.b) {
100 c.bad = true
101 return nil
102 }
103 out := c.b[:n]
104 c.b = c.b[n:]
105 return out
106}
107
108func (c *cursor) u8() int {
109 b := c.take(1)
110 if b == nil {
111 return 0
112 }
113 return int(b[0])
114}
115
116func (c *cursor) u16() int {
117 b := c.take(2)
118 if b == nil {
119 return 0
120 }
121 return int(b[0])<<8 | int(b[1])
122}
123
124// parseClientHelloBody walks a ClientHello body (the bytes after the 4-byte
125// handshake header) and returns the normalized host_name from the
126// server_name extension: "" when there is none, errBadSNI when it is present
127// but unusable.
128func parseClientHelloBody(b []byte) (string, error) {
129 c := &cursor{b: b}
130 c.take(2) // legacy_version
131 c.take(32) // random
132 c.take(c.u8())
133 if cs := c.u16(); cs < 2 || cs%2 != 0 {
134 return "", errHelloMalformed
135 } else {
136 c.take(cs)
137 }
138 c.take(c.u8()) // compression methods
139 if c.bad {
140 return "", errHelloMalformed
141 }
142 if len(c.b) == 0 {
143 return "", nil // no extensions at all
144 }
145 extLen := c.u16()
146 ext := &cursor{b: c.take(extLen)}
147 if c.bad || len(c.b) != 0 { // the extensions end the message: bytes after them are a hello that lies about its own length
148 return "", errHelloMalformed
149 }
150 found := false
151 name := ""
152 for len(ext.b) > 0 {
153 typ := ext.u16()
154 data := &cursor{b: ext.take(ext.u16())}
155 if ext.bad {
156 return "", errHelloMalformed
157 }
158 if typ != 0 { // server_name
159 continue
160 }
161 if found {
162 return "", errHelloMalformed // duplicate extension
163 }
164 found = true
165 listLen := data.u16()
166 list := &cursor{b: data.take(listLen)}
167 if data.bad || len(data.b) != 0 {
168 return "", errHelloMalformed
169 }
170 hostNames := 0
171 for len(list.b) > 0 {
172 nameType := list.u8()
173 raw := list.take(list.u16())
174 if list.bad {
175 return "", errHelloMalformed
176 }
177 if nameType != 0 {
178 continue
179 }
180 hostNames++
181 if hostNames > 1 {
182 return "", errHelloMalformed
183 }
184 n, ok := normalizeName(string(raw))
185 if !ok {
186 return "", errBadSNI
187 }
188 name = n
189 }
190 }
191 return name, nil
192}
193
194// readClientHello reads from c until the ClientHello is complete (or invalid,
195// too large, or the deadline passes). It returns EVERY byte it read from the
196// connection, which the caller must forward to the device first.
197func readClientHello(c net.Conn, timeout time.Duration) (peeked []byte, sni string, err error) {
198 _ = c.SetReadDeadline(time.Now().Add(timeout))
199 defer func() { _ = c.SetReadDeadline(time.Time{}) }()
200
201 buf := make([]byte, 0, 2048)
202 need := 1
203 for {
204 for len(buf) >= need {
205 res := scanClientHello(buf)
206 if res.err != nil {
207 return buf, "", res.err
208 }
209 if res.complete {
210 return buf, res.sni, nil
211 }
212 need = res.need
213 if need > maxPeek {
214 return buf, "", errHelloTooLarge
215 }
216 }
217 if len(buf) == cap(buf) {
218 next := 2 * cap(buf)
219 if next > maxPeek {
220 next = maxPeek
221 }
222 if next <= len(buf) {
223 return buf, "", errHelloTooLarge
224 }
225 grown := make([]byte, len(buf), next)
226 copy(grown, buf)
227 buf = grown
228 }
229 n, rerr := c.Read(buf[len(buf):cap(buf)])
230 buf = buf[:len(buf)+n]
231 if rerr != nil {
232 if n > 0 && len(buf) >= need {
233 continue // decide on what we have before reporting the error
234 }
235 return buf, "", rerr
236 }
237 }
238}
239
240// normalizeName is the one way a server name becomes a routing key, for an SNI and for a Host header alike: lower case, at most one trailing dot
241// removed, then strict LDH. A name the table could hold under two spellings, or that a resolver would read differently from the node, is refused:
242// letters, digits and hyphens only (no underscore), labels of 1..63 bytes that neither start nor end with a hyphen, at most 253 bytes in all, a last
243// label that is not all digits (so no dotted-decimal address in disguise) and not an IP literal. The device's own name always passes: it is one
244// label under a zone.
245func normalizeName(s string) (string, bool) {
246 b := []byte(s)
247 for i, ch := range b {
248 if ch >= 'A' && ch <= 'Z' {
249 b[i] = ch + ('a' - 'A')
250 }
251 }
252 s = string(b)
253 if len(s) > 1 && s[len(s)-1] == '.' {
254 s = s[:len(s)-1]
255 }
256 if s == "" || len(s) > 253 || net.ParseIP(s) != nil {
257 return "", false
258 }
259 start := 0
260 for i := 0; i <= len(s); i++ {
261 if i < len(s) && s[i] != '.' {
262 ch := s[i]
263 if !(ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9' || ch == '-') {
264 return "", false
265 }
266 continue
267 }
268 label := s[start:i]
269 if len(label) == 0 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
270 return "", false
271 }
272 if i == len(s) && strings.Trim(label, "0123456789") == "" {
273 return "", false
274 }
275 start = i + 1
276 }
277 return s, true
278}