]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/tls: clarify group selection logic
authorFilippo Valsorda <filippo@golang.org>
Sat, 18 May 2024 17:35:39 +0000 (19:35 +0200)
committerGopher Robot <gobot@golang.org>
Wed, 22 May 2024 14:19:11 +0000 (14:19 +0000)
I initially thought the logic was broken, but writing the test I
realized it was actually very clever (derogative). It was relying on the
outer loop continuing after a supported match without a key share,
allowing a later key share to override it (but not a later supported
match because of the "if selectedGroup != 0 { continue }").

Replaced the clever loop with two hopefully more understandable loops,
and added a test (which was already passing).

We were however not checking that the selected group is in the supported
list if we found it in key shares first. (This was only a MAY.) Fixed.

Fixes #65686

Change-Id: I09ea44f90167ffa36809deb78255ed039a217b6d
Reviewed-on: https://go-review.googlesource.com/c/go/+/586655
Reviewed-by: Roland Shoemaker <roland@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>

src/crypto/tls/boring_test.go
src/crypto/tls/common.go
src/crypto/tls/conn.go
src/crypto/tls/handshake_client_test.go
src/crypto/tls/handshake_client_tls13.go
src/crypto/tls/handshake_server_test.go
src/crypto/tls/handshake_server_tls13.go
src/crypto/tls/handshake_test.go
src/crypto/tls/testdata/Server-TLSv13-KeySharePreference [new file with mode: 0644]

index 085ff5713ec52f8907a3b761ab64f748ce915c55..77374abe341c2f4c02aea46e835d7a7bdcc8f48f 100644 (file)
@@ -26,33 +26,42 @@ import (
 )
 
 func TestBoringServerProtocolVersion(t *testing.T) {
-       test := func(name string, v uint16, msg string) {
+       test := func(t *testing.T, name string, v uint16, msg string) {
                t.Run(name, func(t *testing.T) {
                        serverConfig := testConfig.Clone()
                        serverConfig.MinVersion = VersionSSL30
-                       clientHello := &clientHelloMsg{
-                               vers:               v,
-                               random:             make([]byte, 32),
-                               cipherSuites:       allCipherSuites(),
-                               compressionMethods: []uint8{compressionNone},
-                               supportedVersions:  []uint16{v},
+                       clientConfig := testConfig.Clone()
+                       clientConfig.MinVersion = v
+                       clientConfig.MaxVersion = v
+                       _, _, err := testHandshake(t, clientConfig, serverConfig)
+                       if msg == "" {
+                               if err != nil {
+                                       t.Fatalf("got error: %v, expected success", err)
+                               }
+                       } else {
+                               if err == nil {
+                                       t.Fatalf("got success, expected error")
+                               }
+                               if !strings.Contains(err.Error(), msg) {
+                                       t.Fatalf("got error %v, expected %q", err, msg)
+                               }
                        }
-                       testClientHelloFailure(t, serverConfig, clientHello, msg)
                })
        }
 
-       test("VersionTLS10", VersionTLS10, "")
-       test("VersionTLS11", VersionTLS11, "")
-       test("VersionTLS12", VersionTLS12, "")
-       test("VersionTLS13", VersionTLS13, "")
-
-       fipstls.Force()
-       defer fipstls.Abandon()
-       test("VersionSSL30", VersionSSL30, "client offered only unsupported versions")
-       test("VersionTLS10", VersionTLS10, "client offered only unsupported versions")
-       test("VersionTLS11", VersionTLS11, "client offered only unsupported versions")
-       test("VersionTLS12", VersionTLS12, "")
-       test("VersionTLS13", VersionTLS13, "client offered only unsupported versions")
+       test(t, "VersionTLS10", VersionTLS10, "")
+       test(t, "VersionTLS11", VersionTLS11, "")
+       test(t, "VersionTLS12", VersionTLS12, "")
+       test(t, "VersionTLS13", VersionTLS13, "")
+
+       t.Run("fipstls", func(t *testing.T) {
+               fipstls.Force()
+               defer fipstls.Abandon()
+               test(t, "VersionTLS10", VersionTLS10, "supported versions")
+               test(t, "VersionTLS11", VersionTLS11, "supported versions")
+               test(t, "VersionTLS12", VersionTLS12, "")
+               test(t, "VersionTLS13", VersionTLS13, "supported versions")
+       })
 }
 
 func isBoringVersion(v uint16) bool {
@@ -154,26 +163,22 @@ func TestBoringServerCurves(t *testing.T) {
 
        for _, curveid := range defaultCurvePreferences {
                t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) {
-                       clientHello := &clientHelloMsg{
-                               vers:               VersionTLS12,
-                               random:             make([]byte, 32),
-                               cipherSuites:       []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
-                               compressionMethods: []uint8{compressionNone},
-                               supportedCurves:    []CurveID{curveid},
-                               supportedPoints:    []uint8{pointFormatUncompressed},
+                       clientConfig := testConfig.Clone()
+                       clientConfig.CurvePreferences = []CurveID{curveid}
+                       if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil {
+                               t.Fatalf("got error: %v, expected success", err)
                        }
 
-                       testClientHello(t, serverConfig, clientHello)
-
                        // With fipstls forced, bad curves should be rejected.
                        t.Run("fipstls", func(t *testing.T) {
                                fipstls.Force()
                                defer fipstls.Abandon()
-                               msg := ""
-                               if !isBoringCurve(curveid) {
-                                       msg = "no cipher suite supported by both client and server"
+                               _, _, err := testHandshake(t, clientConfig, serverConfig)
+                               if err != nil && isBoringCurve(curveid) {
+                                       t.Fatalf("got error: %v, expected success", err)
+                               } else if err == nil && !isBoringCurve(curveid) {
+                                       t.Fatalf("got success, expected error")
                                }
-                               testClientHelloFailure(t, serverConfig, clientHello, msg)
                        })
                })
        }
index 58dc0c231cc7ac1bd12504fe039670506cf6a7d1..34a301340b0431835af7315ad8c4448c7988b384 100644 (file)
@@ -299,6 +299,9 @@ type ConnectionState struct {
 
        // ekm is a closure exposed via ExportKeyingMaterial.
        ekm func(label string, context []byte, length int) ([]byte, error)
+
+       // testingOnlyDidHRR is true if a HelloRetryRequest was sent/received.
+       testingOnlyDidHRR bool
 }
 
 // ExportKeyingMaterial returns length bytes of exported key material in a new
index 30c5f00091029e577230c312e1b35414d0c87359..c44f6513f44c76cf80de35a4ca3cb45456319e90 100644 (file)
@@ -48,6 +48,7 @@ type Conn struct {
        handshakes       int
        extMasterSecret  bool
        didResume        bool // whether this connection was a session resumption
+       didHRR           bool // whether a HelloRetryRequest was sent/received
        cipherSuite      uint16
        ocspResponse     []byte   // stapled OCSP response
        scts             [][]byte // signed certificate timestamps from server
@@ -1608,6 +1609,7 @@ func (c *Conn) connectionStateLocked() ConnectionState {
        state.Version = c.vers
        state.NegotiatedProtocol = c.clientProtocol
        state.DidResume = c.didResume
+       state.testingOnlyDidHRR = c.didHRR
        state.NegotiatedProtocolIsMutual = true
        state.ServerName = c.serverName
        state.CipherSuite = c.cipherSuite
index 157c67ff86b77452afa304d08d20396b354eb8b2..eb0fe368e0bff4cef9faade3a0c1cf29171edabc 100644 (file)
@@ -659,6 +659,12 @@ func TestHandshakeClientHelloRetryRequest(t *testing.T) {
                name:   "HelloRetryRequest",
                args:   []string{"-cipher", "ECDHE-RSA-AES128-GCM-SHA256", "-curves", "P-256"},
                config: config,
+               validate: func(cs ConnectionState) error {
+                       if !cs.testingOnlyDidHRR {
+                               return errors.New("expected HelloRetryRequest")
+                       }
+                       return nil
+               },
        }
 
        runClientTestTLS13(t, test)
index bc8670a6f2b5a59156c549da0a60857e7e407e5f..88ec383bf8351f1b08c444e194eca07a7b5d1ef5 100644 (file)
@@ -307,6 +307,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
                return err
        }
 
+       c.didHRR = true
        return nil
 }
 
index ff0b479f0438c7c2a51c98e105856279fed140ee..813495d7b994970d8270413e5dc902c1fb0331e6 100644 (file)
@@ -52,16 +52,33 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa
        ctx := context.Background()
        conn := Server(s, serverConfig)
        ch, err := conn.readClientHello(ctx)
-       hs := serverHandshakeState{
-               c:           conn,
-               ctx:         ctx,
-               clientHello: ch,
-       }
-       if err == nil {
-               err = hs.processClientHello()
-       }
-       if err == nil {
-               err = hs.pickCipherSuite()
+       if conn.vers == VersionTLS13 {
+               hs := serverHandshakeStateTLS13{
+                       c:           conn,
+                       ctx:         ctx,
+                       clientHello: ch,
+               }
+               if err == nil {
+                       err = hs.processClientHello()
+               }
+               if err == nil {
+                       err = hs.checkForResumption()
+               }
+               if err == nil {
+                       err = hs.pickCertificate()
+               }
+       } else {
+               hs := serverHandshakeState{
+                       c:           conn,
+                       ctx:         ctx,
+                       clientHello: ch,
+               }
+               if err == nil {
+                       err = hs.processClientHello()
+               }
+               if err == nil {
+                       err = hs.pickCipherSuite()
+               }
        }
        s.Close()
        if len(expectedSubStr) == 0 {
@@ -903,10 +920,52 @@ func TestHandshakeServerHelloRetryRequest(t *testing.T) {
                name:    "HelloRetryRequest",
                command: []string{"openssl", "s_client", "-no_ticket", "-ciphersuites", "TLS_CHACHA20_POLY1305_SHA256", "-curves", "X25519:P-256"},
                config:  config,
+               validate: func(cs ConnectionState) error {
+                       if !cs.testingOnlyDidHRR {
+                               return errors.New("expected HelloRetryRequest")
+                       }
+                       return nil
+               },
        }
        runServerTestTLS13(t, test)
 }
 
+// TestHandshakeServerKeySharePreference checks that we prefer a key share even
+// if it's later in the CurvePreferences order.
+func TestHandshakeServerKeySharePreference(t *testing.T) {
+       config := testConfig.Clone()
+       config.CurvePreferences = []CurveID{X25519, CurveP256}
+
+       test := &serverTest{
+               name:    "KeySharePreference",
+               command: []string{"openssl", "s_client", "-no_ticket", "-ciphersuites", "TLS_CHACHA20_POLY1305_SHA256", "-curves", "P-256:X25519"},
+               config:  config,
+               validate: func(cs ConnectionState) error {
+                       if cs.testingOnlyDidHRR {
+                               return errors.New("unexpected HelloRetryRequest")
+                       }
+                       return nil
+               },
+       }
+       runServerTestTLS13(t, test)
+}
+
+// TestHandshakeServerUnsupportedKeyShare tests a client that sends a key share
+// that's not in the supported groups list.
+func TestHandshakeServerUnsupportedKeyShare(t *testing.T) {
+       pk, _ := ecdh.X25519().GenerateKey(rand.Reader)
+       clientHello := &clientHelloMsg{
+               vers:               VersionTLS12,
+               random:             make([]byte, 32),
+               supportedVersions:  []uint16{VersionTLS13},
+               cipherSuites:       []uint16{TLS_CHACHA20_POLY1305_SHA256},
+               compressionMethods: []uint8{compressionNone},
+               keyShares:          []keyShare{{group: X25519, data: pk.PublicKey().Bytes()}},
+               supportedCurves:    []CurveID{CurveP256},
+       }
+       testClientHelloFailure(t, testConfig, clientHello, "client sent key share for group it does not support")
+}
+
 func TestHandshakeServerALPN(t *testing.T) {
        config := testConfig.Clone()
        config.NextProtos = []string{"proto1", "proto2"}
@@ -1932,6 +1991,7 @@ func TestAESCipherReorderingTLS13(t *testing.T) {
                                        supportedVersions:  []uint16{VersionTLS13},
                                        compressionMethods: []uint8{compressionNone},
                                        keyShares:          []keyShare{{group: X25519, data: pk.PublicKey().Bytes()}},
+                                       supportedCurves:    []CurveID{X25519},
                                },
                        }
 
index 7f15d05b28817020f248fc72b136e0b2024dcca0..a7d3890ba9c8fc2f7aae48625c61608d1a60b919 100644 (file)
@@ -14,6 +14,7 @@ import (
        "hash"
        "internal/byteorder"
        "io"
+       "slices"
        "time"
 )
 
@@ -181,21 +182,25 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
        // groups with a key share, to avoid a HelloRetryRequest round-trip.
        var selectedGroup CurveID
        var clientKeyShare *keyShare
-GroupSelection:
-       for _, preferredGroup := range c.config.curvePreferences() {
-               for _, ks := range hs.clientHello.keyShares {
-                       if ks.group == preferredGroup {
-                               selectedGroup = ks.group
-                               clientKeyShare = &ks
-                               break GroupSelection
+       preferredGroups := c.config.curvePreferences()
+       for _, preferredGroup := range preferredGroups {
+               ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool {
+                       return ks.group == preferredGroup
+               })
+               if ki != -1 {
+                       clientKeyShare = &hs.clientHello.keyShares[ki]
+                       selectedGroup = clientKeyShare.group
+                       if !slices.Contains(hs.clientHello.supportedCurves, selectedGroup) {
+                               c.sendAlert(alertIllegalParameter)
+                               return errors.New("tls: client sent key share for group it does not support")
                        }
+                       break
                }
-               if selectedGroup != 0 {
-                       continue
-               }
-               for _, group := range hs.clientHello.supportedCurves {
-                       if group == preferredGroup {
-                               selectedGroup = group
+       }
+       if selectedGroup == 0 {
+               for _, preferredGroup := range preferredGroups {
+                       if slices.Contains(hs.clientHello.supportedCurves, preferredGroup) {
+                               selectedGroup = preferredGroup
                                break
                        }
                }
@@ -532,6 +537,7 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID)
                return errors.New("tls: client illegally modified second ClientHello")
        }
 
+       c.didHRR = true
        hs.clientHello = clientHello
        return nil
 }
index e365a79cf7cee682463f5b2609173a942ee31b80..f5e467b8b05d2629ff61bbef79d4e2047e6bde17 100644 (file)
@@ -296,6 +296,8 @@ Dialing:
 
                        case c2 := <-localListener.ch:
                                if c2.RemoteAddr().String() == c1.LocalAddr().String() {
+                                       t.Cleanup(func() { c1.Close() })
+                                       t.Cleanup(func() { c2.Close() })
                                        return c1, c2
                                }
                                t.Logf("localPipe: unexpected connection: %v != %v", c2.RemoteAddr(), c1.LocalAddr())
@@ -399,7 +401,7 @@ func runMain(m *testing.M) int {
 func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverState, clientState ConnectionState, err error) {
        const sentinel = "SENTINEL\n"
        c, s := localPipe(t)
-       errChan := make(chan error)
+       errChan := make(chan error, 1)
        go func() {
                cli := Client(c, clientConfig)
                err := cli.Handshake()
@@ -408,7 +410,7 @@ func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverStat
                        c.Close()
                        return
                }
-               defer cli.Close()
+               defer func() { errChan <- nil }()
                clientState = cli.ConnectionState()
                buf, err := io.ReadAll(cli)
                if err != nil {
@@ -417,7 +419,9 @@ func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverStat
                if got := string(buf); got != sentinel {
                        t.Errorf("read %q from TLS connection, but expected %q", got, sentinel)
                }
-               errChan <- nil
+               if err := cli.Close(); err != nil {
+                       t.Errorf("failed to call cli.Close: %v", err)
+               }
        }()
        server := Server(s, serverConfig)
        err = server.Handshake()
@@ -429,11 +433,11 @@ func testHandshake(t *testing.T, clientConfig, serverConfig *Config) (serverStat
                if err := server.Close(); err != nil {
                        t.Errorf("failed to call server.Close: %v", err)
                }
-               err = <-errChan
        } else {
+               err = fmt.Errorf("server: %v", err)
                s.Close()
-               <-errChan
        }
+       err = errors.Join(err, <-errChan)
        return
 }
 
diff --git a/src/crypto/tls/testdata/Server-TLSv13-KeySharePreference b/src/crypto/tls/testdata/Server-TLSv13-KeySharePreference
new file mode 100644 (file)
index 0000000..fc79cc6
--- /dev/null
@@ -0,0 +1,101 @@
+>>> Flow 1 (client to server)
+00000000  16 03 01 00 e5 01 00 00  e1 03 03 58 2d 84 f2 4f  |...........X-..O|
+00000010  6d 10 9d f6 5a 67 c7 92  78 e6 7d 96 96 fe db 37  |m...Zg..x.}....7|
+00000020  07 44 e3 2e 85 3f 1a 5f  f1 2a 24 20 cb e8 e8 c4  |.D...?._.*$ ....|
+00000030  e9 28 01 21 9f 82 d2 da  6b 3e a0 7f 0d 20 95 12  |.(.!....k>... ..|
+00000040  39 5b 3f f8 04 86 df 9b  72 3b 74 e2 00 04 13 03  |9[?.....r;t.....|
+00000050  00 ff 01 00 00 94 00 0b  00 04 03 00 01 02 00 0a  |................|
+00000060  00 06 00 04 00 17 00 1d  00 16 00 00 00 17 00 00  |................|
+00000070  00 0d 00 1e 00 1c 04 03  05 03 06 03 08 07 08 08  |................|
+00000080  08 09 08 0a 08 0b 08 04  08 05 08 06 04 01 05 01  |................|
+00000090  06 01 00 2b 00 03 02 03  04 00 2d 00 02 01 01 00  |...+......-.....|
+000000a0  33 00 47 00 45 00 17 00  41 04 63 03 f6 90 df 1f  |3.G.E...A.c.....|
+000000b0  d0 03 96 24 21 3d e4 c0  5d 38 5f 53 85 a4 9c d8  |...$!=..]8_S....|
+000000c0  f7 b0 9c 64 5f 53 c3 66  9b f5 e0 5a 60 0b b2 e8  |...d_S.f...Z`...|
+000000d0  2b d0 6a a2 ff 1d 07 8b  5f a8 37 e4 74 35 15 b1  |+.j....._.7.t5..|
+000000e0  06 de 79 3b f4 69 52 2a  ad 66                    |..y;.iR*.f|
+>>> Flow 2 (server to client)
+00000000  16 03 03 00 9b 02 00 00  97 03 03 00 00 00 00 00  |................|
+00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
+00000020  00 00 00 00 00 00 00 00  00 00 00 20 cb e8 e8 c4  |........... ....|
+00000030  e9 28 01 21 9f 82 d2 da  6b 3e a0 7f 0d 20 95 12  |.(.!....k>... ..|
+00000040  39 5b 3f f8 04 86 df 9b  72 3b 74 e2 13 03 00 00  |9[?.....r;t.....|
+00000050  4f 00 2b 00 02 03 04 00  33 00 45 00 17 00 41 04  |O.+.....3.E...A.|
+00000060  1e 18 37 ef 0d 19 51 88  35 75 71 b5 e5 54 5b 12  |..7...Q.5uq..T[.|
+00000070  2e 8f 09 67 fd a7 24 20  3e b2 56 1c ce 97 28 5e  |...g..$ >.V...(^|
+00000080  f8 2b 2d 4f 9e f1 07 9f  6c 4b 5b 83 56 e2 32 42  |.+-O....lK[.V.2B|
+00000090  e9 58 b6 d7 49 a6 b5 68  1a 41 03 56 6b dc 5a 89  |.X..I..h.A.Vk.Z.|
+000000a0  14 03 03 00 01 01 17 03  03 00 17 f8 6a 35 d1 d7  |............j5..|
+000000b0  a0 09 48 20 66 ea 1d 4e  a4 05 2b d6 24 ea 0d cb  |..H f..N..+.$...|
+000000c0  68 8b 17 03 03 02 6d 16  c5 3b c2 80 85 49 51 1f  |h.....m..;...IQ.|
+000000d0  4e 0e 8a 11 63 95 9b 83  46 a9 19 43 90 ee 55 ae  |N...c...F..C..U.|
+000000e0  4f 09 ae cf fd 64 dd a9  c0 af 31 fd 95 29 20 9f  |O....d....1..) .|
+000000f0  da d4 27 97 2c da da 2c  c8 e7 e5 c2 6b 24 5f dd  |..'.,..,....k$_.|
+00000100  68 86 b1 9b 01 95 8a 78  bb 10 26 66 24 d4 59 df  |h......x..&f$.Y.|
+00000110  6d b4 56 c4 23 56 5d 3c  fc 6d 69 ce eb e2 9c 30  |m.V.#V]<.mi....0|
+00000120  da 34 39 32 80 a7 26 d6  e2 11 33 a5 c1 5d 75 07  |.492..&...3..]u.|
+00000130  f5 f6 98 9e f2 26 49 96  52 fb 56 6d 34 67 45 15  |.....&I.R.Vm4gE.|
+00000140  97 0d 0d 5e 7e 63 6b 42  0e 6f 2c 3b 11 06 d5 a4  |...^~ckB.o,;....|
+00000150  00 92 2d 39 62 ea f4 28  04 ee e4 f4 78 f4 d2 72  |..-9b..(....x..r|
+00000160  f7 65 61 43 76 12 59 90  a3 15 71 07 78 dc 61 cf  |.eaCv.Y...q.x.a.|
+00000170  62 6e 7d 2d 7e b0 0f e3  ca 21 1b 13 96 ef 96 23  |bn}-~....!.....#|
+00000180  86 31 2a 53 b0 13 a3 23  29 74 2c 98 3b 75 a2 96  |.1*S...#)t,.;u..|
+00000190  7b 4a cb 22 f3 63 84 17  9b 25 a8 10 75 88 3c fd  |{J.".c...%..u.<.|
+000001a0  d5 49 c3 97 c3 78 83 c1  24 f3 7f 21 63 e8 b2 01  |.I...x..$..!c...|
+000001b0  b0 92 b8 8b f2 83 64 2f  a8 8f b9 18 14 44 54 6a  |......d/.....DTj|
+000001c0  a9 d6 68 5c be 50 69 8d  35 16 5e 8d 9a 6a f0 5b  |..h\.Pi.5.^..j.[|
+000001d0  a4 f1 5b 51 3e 5b d4 d1  41 06 3c c7 09 27 96 a8  |..[Q>[..A.<..'..|
+000001e0  81 07 d2 33 27 3c a6 a9  bb 7b 80 28 58 e4 b0 dd  |...3'<...{.(X...|
+000001f0  a8 9e b2 61 ed 5b 57 c8  b2 a2 7f 6b f7 0f ee 3d  |...a.[W....k...=|
+00000200  9c 6d a1 76 55 3e af aa  17 c8 a8 ec c6 14 31 77  |.m.vU>........1w|
+00000210  38 a3 9c 15 3f 0e cc 63  43 8f b0 c1 14 97 ee 85  |8...?..cC.......|
+00000220  30 08 b1 95 0c 7f 5b 95  4d 4f 18 26 d8 45 cb b4  |0.....[.MO.&.E..|
+00000230  b7 1b f5 a5 d5 e5 21 da  53 88 57 09 ed 30 fe d1  |......!.S.W..0..|
+00000240  ac ee c7 80 ee a9 96 31  92 4c a1 e2 2c a8 f5 b1  |.......1.L..,...|
+00000250  d9 3c bd c8 e6 1b ba 7c  91 d6 6c d4 ae 0a 15 50  |.<.....|..l....P|
+00000260  b9 24 80 14 ff 28 98 94  a7 7f d4 13 16 1f 03 bf  |.$...(..........|
+00000270  72 83 94 a3 8a 6d b6 d7  b2 c7 22 56 38 f2 15 e1  |r....m...."V8...|
+00000280  ea 1f 78 d0 ff ac c4 19  54 a1 c6 2d 8a da cd f3  |..x.....T..-....|
+00000290  6e 45 b3 a4 dc e3 a0 6e  f8 18 af a6 c6 20 ce a7  |nE.....n..... ..|
+000002a0  eb dc 42 06 bd d4 bf a0  ef 36 4c f6 38 42 3d f7  |..B......6L.8B=.|
+000002b0  a3 a5 ac 4d b3 71 36 9b  00 ee 1f 40 fa bc b3 d4  |...M.q6....@....|
+000002c0  5b 49 79 4f 16 fd 3e 4f  ab 8e cc 92 7d f3 1d c7  |[IyO..>O....}...|
+000002d0  13 76 49 56 1c 59 13 56  3b 6b 33 ed 1a 85 9c b7  |.vIV.Y.V;k3.....|
+000002e0  a1 55 84 83 a1 df d6 53  0c c1 b6 63 63 b2 58 84  |.U.....S...cc.X.|
+000002f0  12 f6 99 7a ac c5 ee 53  69 9e 86 76 88 aa 7b 2f  |...z...Si..v..{/|
+00000300  f8 48 0b 05 e1 2a 1c 0d  56 ae 79 9e 68 4f b5 85  |.H...*..V.y.hO..|
+00000310  df cc 11 05 33 94 55 e6  16 d4 d5 78 b4 d1 c3 2c  |....3.U....x...,|
+00000320  2d 3c ac 45 6e fd 1e e0  79 5c 23 c3 57 66 3e d2  |-<.En...y\#.Wf>.|
+00000330  22 39 21 df 17 03 03 00  99 a9 e3 ac d5 82 cc bd  |"9!.............|
+00000340  74 c3 92 13 4d 32 fc ff  e4 63 ec ea 81 40 47 bb  |t...M2...c...@G.|
+00000350  3f ad 65 d4 fe 4f 0c c0  6a b0 78 c4 4f 0d e8 73  |?.e..O..j.x.O..s|
+00000360  7c ee 9b ff 61 f8 4b 17  32 92 5d e5 49 ea 7b 38  ||...a.K.2.].I.{8|
+00000370  6b db a2 4b 64 1d 7f 42  ce 4d f0 d8 dc 9c 93 f4  |k..Kd..B.M......|
+00000380  23 5d d5 dd 34 90 51 42  3d 0e bf 69 31 d8 0f e6  |#]..4.QB=..i1...|
+00000390  14 5a ec 52 1e d5 41 a6  25 7c 9b 40 f5 92 58 17  |.Z.R..A.%|.@..X.|
+000003a0  80 68 a2 43 58 d3 d9 7c  7a 1f 90 4f a5 08 f8 0f  |.h.CX..|z..O....|
+000003b0  31 7b bd cc 74 3d f8 73  7d 0a 6c 12 25 53 b3 99  |1{..t=.s}.l.%S..|
+000003c0  c7 f2 4c 99 5d 7c 56 cd  29 5c 30 91 93 6f a2 00  |..L.]|V.)\0..o..|
+000003d0  c4 97 17 03 03 00 35 81  b3 9e 92 c5 dc 08 b3 f3  |......5.........|
+000003e0  70 56 52 40 16 d4 75 34  1e e8 b7 ce 5e 5a 7e 47  |pVR@..u4....^Z~G|
+000003f0  ec d0 75 68 24 06 a6 1b  87 73 6c b0 ff e4 3c 6d  |..uh$....sl...<m|
+00000400  80 7e 02 d3 c1 83 78 e3  82 fe 22 d2 17 03 03 00  |.~....x...".....|
+00000410  8b 20 dd 38 a2 4d 01 39  2b 53 0a 62 f7 51 9a de  |. .8.M.9+S.b.Q..|
+00000420  10 5e 42 1f a2 13 e5 f5  b0 cb 69 21 eb ac 6e 4d  |.^B.......i!..nM|
+00000430  1d 00 94 e4 29 f1 c6 c0  0c 58 7e ca 99 e5 5d 77  |....)....X~...]w|
+00000440  c3 3a 23 8e b8 a8 19 6e  35 ec e3 51 61 82 23 2f  |.:#....n5..Qa.#/|
+00000450  3e af 1a e9 6b 4f 43 a9  60 d6 55 d6 75 f5 a6 84  |>...kOC.`.U.u...|
+00000460  27 64 8b 0f 15 db 95 47  36 b3 14 e0 da a2 21 1e  |'d.....G6.....!.|
+00000470  bf 0d 40 79 c8 6c 3b 9f  eb 96 1a cf 9d 09 f7 a9  |..@y.l;.........|
+00000480  22 e5 13 c4 2a 69 5e 95  d1 40 e5 00 26 20 ae 16  |"...*i^..@..& ..|
+00000490  55 27 51 a1 c7 e3 2e 1d  32 6c d5 0d              |U'Q.....2l..|
+>>> Flow 3 (client to server)
+00000000  14 03 03 00 01 01 17 03  03 00 35 71 16 57 df 09  |..........5q.W..|
+00000010  d7 73 e4 f2 c4 8a 10 b1  d8 73 dc 5b 87 8f 56 51  |.s.......s.[..VQ|
+00000020  6a ed 61 66 c9 d4 0d fe  28 0c 6f c7 4d ef e6 90  |j.af....(.o.M...|
+00000030  ba e2 fb c3 c9 4a 94 a2  e5 7f 23 e2 66 2b 4f 9e  |.....J....#.f+O.|
+>>> Flow 4 (server to client)
+00000000  17 03 03 00 1e 6a c8 6d  0d b6 f7 c8 33 cd c6 25  |.....j.m....3..%|
+00000010  98 0e bb ac de 69 61 9d  ec a3 c0 be 7e 53 44 cb  |.....ia.....~SD.|
+00000020  1f d5 97 17 03 03 00 13  be 18 cc 16 91 88 1e d1  |................|
+00000030  b5 7c 58 17 fb 39 b2 80  76 7b a8                 |.|X..9..v{.|