Source file
src/crypto/tls/bogo_shim_test.go
1
2
3
4
5 package tls
6
7 import (
8 "bytes"
9 "crypto/internal/cryptotest"
10 "crypto/x509"
11 "encoding/base64"
12 "encoding/json"
13 "encoding/pem"
14 "flag"
15 "fmt"
16 "internal/byteorder"
17 "internal/testenv"
18 "io"
19 "log"
20 "net"
21 "os"
22 "path/filepath"
23 "runtime"
24 "slices"
25 "strconv"
26 "strings"
27 "testing"
28
29 "golang.org/x/crypto/cryptobyte"
30 )
31
32 var (
33 port = flag.String("port", "", "")
34 server = flag.Bool("server", false, "")
35
36 isHandshakerSupported = flag.Bool("is-handshaker-supported", false, "")
37
38 keyfile = flag.String("key-file", "", "")
39 certfile = flag.String("cert-file", "", "")
40
41 trustCert = flag.String("trust-cert", "", "")
42
43 minVersion = flag.Int("min-version", VersionSSL30, "")
44 maxVersion = flag.Int("max-version", VersionTLS13, "")
45 expectVersion = flag.Int("expect-version", 0, "")
46
47 noTLS1 = flag.Bool("no-tls1", false, "")
48 noTLS11 = flag.Bool("no-tls11", false, "")
49 noTLS12 = flag.Bool("no-tls12", false, "")
50 noTLS13 = flag.Bool("no-tls13", false, "")
51
52 requireAnyClientCertificate = flag.Bool("require-any-client-certificate", false, "")
53
54 shimWritesFirst = flag.Bool("shim-writes-first", false, "")
55
56 resumeCount = flag.Int("resume-count", 0, "")
57
58 curves = flagStringSlice("curves", "")
59 expectedCurve = flag.String("expect-curve-id", "", "")
60
61 shimID = flag.Uint64("shim-id", 0, "")
62 _ = flag.Bool("ipv6", false, "")
63
64 echConfigListB64 = flag.String("ech-config-list", "", "")
65 expectECHAccepted = flag.Bool("expect-ech-accept", false, "")
66 expectHRR = flag.Bool("expect-hrr", false, "")
67 expectNoHRR = flag.Bool("expect-no-hrr", false, "")
68 expectedECHRetryConfigs = flag.String("expect-ech-retry-configs", "", "")
69 expectNoECHRetryConfigs = flag.Bool("expect-no-ech-retry-configs", false, "")
70 onInitialExpectECHAccepted = flag.Bool("on-initial-expect-ech-accept", false, "")
71 _ = flag.Bool("expect-no-ech-name-override", false, "")
72 _ = flag.String("expect-ech-name-override", "", "")
73 _ = flag.Bool("reverify-on-resume", false, "")
74 onResumeECHConfigListB64 = flag.String("on-resume-ech-config-list", "", "")
75 _ = flag.Bool("on-resume-expect-reject-early-data", false, "")
76 onResumeExpectECHAccepted = flag.Bool("on-resume-expect-ech-accept", false, "")
77 _ = flag.Bool("on-resume-expect-no-ech-name-override", false, "")
78 expectedServerName = flag.String("expect-server-name", "", "")
79 echServerConfig = flagStringSlice("ech-server-config", "")
80 echServerKey = flagStringSlice("ech-server-key", "")
81 echServerRetryConfig = flagStringSlice("ech-is-retry-config", "")
82
83 expectSessionMiss = flag.Bool("expect-session-miss", false, "")
84
85 _ = flag.Bool("enable-early-data", false, "")
86 _ = flag.Bool("on-resume-expect-accept-early-data", false, "")
87 _ = flag.Bool("expect-ticket-supports-early-data", false, "")
88 _ = flag.Bool("on-resume-shim-writes-first", false, "")
89
90 advertiseALPN = flag.String("advertise-alpn", "", "")
91 expectALPN = flag.String("expect-alpn", "", "")
92 rejectALPN = flag.Bool("reject-alpn", false, "")
93 declineALPN = flag.Bool("decline-alpn", false, "")
94 expectAdvertisedALPN = flag.String("expect-advertised-alpn", "", "")
95 selectALPN = flag.String("select-alpn", "", "")
96
97 hostName = flag.String("host-name", "", "")
98
99 verifyPeer = flag.Bool("verify-peer", false, "")
100 _ = flag.Bool("use-custom-verify-callback", false, "")
101
102 waitForDebugger = flag.Bool("wait-for-debugger", false, "")
103 )
104
105 type stringSlice []string
106
107 func flagStringSlice(name, usage string) *stringSlice {
108 f := &stringSlice{}
109 flag.Var(f, name, usage)
110 return f
111 }
112
113 func (saf *stringSlice) String() string {
114 return strings.Join(*saf, ",")
115 }
116
117 func (saf *stringSlice) Set(s string) error {
118 *saf = append(*saf, s)
119 return nil
120 }
121
122 func bogoShim() {
123 if *isHandshakerSupported {
124 fmt.Println("No")
125 return
126 }
127
128 cfg := &Config{
129 ServerName: "test",
130
131 MinVersion: uint16(*minVersion),
132 MaxVersion: uint16(*maxVersion),
133
134 ClientSessionCache: NewLRUClientSessionCache(0),
135
136 GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) {
137
138 if *expectAdvertisedALPN != "" {
139
140 s := cryptobyte.String(*expectAdvertisedALPN)
141
142 var expectedALPNs []string
143
144 for !s.Empty() {
145 var alpn cryptobyte.String
146 if !s.ReadUint8LengthPrefixed(&alpn) {
147 return nil, fmt.Errorf("unexpected error while parsing arguments for -expect-advertised-alpn")
148 }
149 expectedALPNs = append(expectedALPNs, string(alpn))
150 }
151
152 if !slices.Equal(chi.SupportedProtos, expectedALPNs) {
153 return nil, fmt.Errorf("unexpected ALPN: got %q, want %q", chi.SupportedProtos, expectedALPNs)
154 }
155 }
156 return nil, nil
157 },
158 }
159
160 if *noTLS1 {
161 cfg.MinVersion = VersionTLS11
162 if *noTLS11 {
163 cfg.MinVersion = VersionTLS12
164 if *noTLS12 {
165 cfg.MinVersion = VersionTLS13
166 if *noTLS13 {
167 log.Fatalf("no supported versions enabled")
168 }
169 }
170 }
171 } else if *noTLS13 {
172 cfg.MaxVersion = VersionTLS12
173 if *noTLS12 {
174 cfg.MaxVersion = VersionTLS11
175 if *noTLS11 {
176 cfg.MaxVersion = VersionTLS10
177 if *noTLS1 {
178 log.Fatalf("no supported versions enabled")
179 }
180 }
181 }
182 }
183
184 if *advertiseALPN != "" {
185 alpns := *advertiseALPN
186 for len(alpns) > 0 {
187 alpnLen := int(alpns[0])
188 cfg.NextProtos = append(cfg.NextProtos, alpns[1:1+alpnLen])
189 alpns = alpns[alpnLen+1:]
190 }
191 }
192
193 if *rejectALPN {
194 cfg.NextProtos = []string{"unnegotiableprotocol"}
195 }
196
197 if *declineALPN {
198 cfg.NextProtos = []string{}
199 }
200 if *selectALPN != "" {
201 cfg.NextProtos = []string{*selectALPN}
202 }
203
204 if *hostName != "" {
205 cfg.ServerName = *hostName
206 }
207
208 if *keyfile != "" || *certfile != "" {
209 pair, err := LoadX509KeyPair(*certfile, *keyfile)
210 if err != nil {
211 log.Fatalf("load key-file err: %s", err)
212 }
213 cfg.Certificates = []Certificate{pair}
214 }
215 if *trustCert != "" {
216 pool := x509.NewCertPool()
217 certFile, err := os.ReadFile(*trustCert)
218 if err != nil {
219 log.Fatalf("load trust-cert err: %s", err)
220 }
221 block, _ := pem.Decode(certFile)
222 cert, err := x509.ParseCertificate(block.Bytes)
223 if err != nil {
224 log.Fatalf("parse trust-cert err: %s", err)
225 }
226 pool.AddCert(cert)
227 cfg.RootCAs = pool
228 }
229
230 if *requireAnyClientCertificate {
231 cfg.ClientAuth = RequireAnyClientCert
232 }
233 if *verifyPeer {
234 cfg.ClientAuth = VerifyClientCertIfGiven
235 }
236
237 if *echConfigListB64 != "" {
238 echConfigList, err := base64.StdEncoding.DecodeString(*echConfigListB64)
239 if err != nil {
240 log.Fatalf("parse ech-config-list err: %s", err)
241 }
242 cfg.EncryptedClientHelloConfigList = echConfigList
243 cfg.MinVersion = VersionTLS13
244 }
245
246 if len(*curves) != 0 {
247 for _, curveStr := range *curves {
248 id, err := strconv.Atoi(curveStr)
249 if err != nil {
250 log.Fatalf("failed to parse curve id %q: %s", curveStr, err)
251 }
252 cfg.CurvePreferences = append(cfg.CurvePreferences, CurveID(id))
253 }
254 }
255
256 if len(*echServerConfig) != 0 {
257 if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) {
258 log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch")
259 }
260
261 for i, c := range *echServerConfig {
262 configBytes, err := base64.StdEncoding.DecodeString(c)
263 if err != nil {
264 log.Fatalf("parse ech-server-config err: %s", err)
265 }
266 privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i])
267 if err != nil {
268 log.Fatalf("parse ech-server-key err: %s", err)
269 }
270
271 cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{
272 Config: configBytes,
273 PrivateKey: privBytes,
274 SendAsRetry: (*echServerRetryConfig)[i] == "1",
275 })
276 }
277 }
278
279 for i := 0; i < *resumeCount+1; i++ {
280 if i > 0 && (*onResumeECHConfigListB64 != "") {
281 echConfigList, err := base64.StdEncoding.DecodeString(*onResumeECHConfigListB64)
282 if err != nil {
283 log.Fatalf("parse ech-config-list err: %s", err)
284 }
285 cfg.EncryptedClientHelloConfigList = echConfigList
286 }
287
288 conn, err := net.Dial("tcp", net.JoinHostPort("localhost", *port))
289 if err != nil {
290 log.Fatalf("dial err: %s", err)
291 }
292 defer conn.Close()
293
294
295 shimIDBytes := make([]byte, 8)
296 byteorder.LEPutUint64(shimIDBytes, *shimID)
297 if _, err := conn.Write(shimIDBytes); err != nil {
298 log.Fatalf("failed to write shim id: %s", err)
299 }
300
301 var tlsConn *Conn
302 if *server {
303 tlsConn = Server(conn, cfg)
304 } else {
305 tlsConn = Client(conn, cfg)
306 }
307
308 if i == 0 && *shimWritesFirst {
309 if _, err := tlsConn.Write([]byte("hello")); err != nil {
310 log.Fatalf("write err: %s", err)
311 }
312 }
313
314
315
316 if *waitForDebugger {
317 pauseProcess()
318 }
319
320 for {
321 buf := make([]byte, 500)
322 var n int
323 n, err = tlsConn.Read(buf)
324 if err != nil {
325 break
326 }
327 buf = buf[:n]
328 for i := range buf {
329 buf[i] ^= 0xff
330 }
331 if _, err = tlsConn.Write(buf); err != nil {
332 break
333 }
334 }
335 if err != io.EOF {
336 retryErr, ok := err.(*ECHRejectionError)
337 if !ok {
338 log.Fatalf("unexpected error type returned: %v", err)
339 }
340 if *expectNoECHRetryConfigs && len(retryErr.RetryConfigList) > 0 {
341 log.Fatalf("expected no ECH retry configs, got some")
342 }
343 if *expectedECHRetryConfigs != "" {
344 expectedRetryConfigs, err := base64.StdEncoding.DecodeString(*expectedECHRetryConfigs)
345 if err != nil {
346 log.Fatalf("failed to decode expected retry configs: %s", err)
347 }
348 if !bytes.Equal(retryErr.RetryConfigList, expectedRetryConfigs) {
349 log.Fatalf("unexpected retry list returned: got %x, want %x", retryErr.RetryConfigList, expectedRetryConfigs)
350 }
351 }
352 log.Fatalf("conn error: %s", err)
353 }
354
355 cs := tlsConn.ConnectionState()
356 if cs.HandshakeComplete {
357 if *expectALPN != "" && cs.NegotiatedProtocol != *expectALPN {
358 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *expectALPN, cs.NegotiatedProtocol)
359 }
360
361 if *selectALPN != "" && cs.NegotiatedProtocol != *selectALPN {
362 log.Fatalf("unexpected protocol negotiated: want %q, got %q", *selectALPN, cs.NegotiatedProtocol)
363 }
364
365 if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
366 log.Fatalf("expected ssl version %q, got %q", uint16(*expectVersion), cs.Version)
367 }
368 if *declineALPN && cs.NegotiatedProtocol != "" {
369 log.Fatal("unexpected ALPN protocol")
370 }
371 if *expectECHAccepted && !cs.ECHAccepted {
372 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
373 } else if i == 0 && *onInitialExpectECHAccepted && !cs.ECHAccepted {
374 log.Fatal("expected ECH to be accepted, but connection state shows it was not")
375 } else if i > 0 && *onResumeExpectECHAccepted && !cs.ECHAccepted {
376 log.Fatal("expected ECH to be accepted on resumption, but connection state shows it was not")
377 } else if i == 0 && !*expectECHAccepted && cs.ECHAccepted {
378 log.Fatal("did not expect ECH, but it was accepted")
379 }
380
381 if *expectHRR && !cs.testingOnlyDidHRR {
382 log.Fatal("expected HRR but did not do it")
383 }
384
385 if *expectNoHRR && cs.testingOnlyDidHRR {
386 log.Fatal("expected no HRR but did do it")
387 }
388
389 if *expectSessionMiss && cs.DidResume {
390 log.Fatal("unexpected session resumption")
391 }
392
393 if *expectedServerName != "" && cs.ServerName != *expectedServerName {
394 log.Fatalf("unexpected server name: got %q, want %q", cs.ServerName, *expectedServerName)
395 }
396 }
397
398 if *expectedCurve != "" {
399 expectedCurveID, err := strconv.Atoi(*expectedCurve)
400 if err != nil {
401 log.Fatalf("failed to parse -expect-curve-id: %s", err)
402 }
403 if tlsConn.curveID != CurveID(expectedCurveID) {
404 log.Fatalf("unexpected curve id: want %d, got %d", expectedCurveID, tlsConn.curveID)
405 }
406 }
407 }
408 }
409
410 func TestBogoSuite(t *testing.T) {
411 if testing.Short() {
412 t.Skip("skipping in short mode")
413 }
414 if testenv.Builder() != "" && runtime.GOOS == "windows" {
415 t.Skip("#66913: windows network connections are flakey on builders")
416 }
417 skipFIPS(t)
418
419
420
421
422
423 if _, err := os.Stat("bogo_config.json"); err != nil {
424 t.Fatal(err)
425 }
426
427 var bogoDir string
428 if *bogoLocalDir != "" {
429 bogoDir = *bogoLocalDir
430 } else {
431 const boringsslModVer = "v0.0.0-20241120195446-5cce3fbd23e1"
432 bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer)
433 }
434
435 cwd, err := os.Getwd()
436 if err != nil {
437 t.Fatal(err)
438 }
439
440 resultsFile := filepath.Join(t.TempDir(), "results.json")
441
442 args := []string{
443 "test",
444 ".",
445 fmt.Sprintf("-shim-config=%s", filepath.Join(cwd, "bogo_config.json")),
446 fmt.Sprintf("-shim-path=%s", os.Args[0]),
447 "-shim-extra-flags=-bogo-mode",
448 "-allow-unimplemented",
449 "-loose-errors",
450 fmt.Sprintf("-json-output=%s", resultsFile),
451 }
452 if *bogoFilter != "" {
453 args = append(args, fmt.Sprintf("-test=%s", *bogoFilter))
454 }
455
456 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
457 out := &strings.Builder{}
458 cmd.Stderr = out
459 cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner")
460 err = cmd.Run()
461
462
463
464
465
466
467 resultsJSON, jsonErr := os.ReadFile(resultsFile)
468 if jsonErr != nil {
469 if err != nil {
470 t.Fatalf("bogo failed: %s\n%s", err, out)
471 }
472 t.Fatalf("failed to read results JSON file: %s", jsonErr)
473 }
474
475 var results bogoResults
476 if err := json.Unmarshal(resultsJSON, &results); err != nil {
477 t.Fatalf("failed to parse results JSON: %s", err)
478 }
479
480
481
482
483 assertResults := map[string]string{
484 "CurveTest-Client-MLKEM-TLS13": "PASS",
485 "CurveTest-Server-MLKEM-TLS13": "PASS",
486 }
487
488 for name, result := range results.Tests {
489
490 t.Run(name, func(t *testing.T) {
491 if result.Actual == "FAIL" && result.IsUnexpected {
492 t.Fatal(result.Error)
493 }
494 if expectedResult, ok := assertResults[name]; ok && expectedResult != result.Actual {
495 t.Fatalf("unexpected result: got %s, want %s", result.Actual, assertResults[name])
496 }
497 delete(assertResults, name)
498 if result.Actual == "SKIP" {
499 t.Skip()
500 }
501 })
502 }
503 if *bogoFilter == "" {
504
505 for name, expectedResult := range assertResults {
506 t.Run(name, func(t *testing.T) {
507 t.Fatalf("expected test to run with result %s, but it was not present in the test results", expectedResult)
508 })
509 }
510 }
511 }
512
513
514 type bogoResults struct {
515 Version int `json:"version"`
516 Interrupted bool `json:"interrupted"`
517 PathDelimiter string `json:"path_delimiter"`
518 SecondsSinceEpoch float64 `json:"seconds_since_epoch"`
519 NumFailuresByType map[string]int `json:"num_failures_by_type"`
520 Tests map[string]struct {
521 Actual string `json:"actual"`
522 Expected string `json:"expected"`
523 IsUnexpected bool `json:"is_unexpected"`
524 Error string `json:"error,omitempty"`
525 } `json:"tests"`
526 }
527
View as plain text