crypto/tls: clarify group selection logic

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>
This commit is contained in:
Filippo Valsorda 2024-05-18 19:35:39 +02:00 committed by Gopher Robot
parent 2a85364a09
commit 245de0a13b
9 changed files with 248 additions and 60 deletions

View 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, "")
test(t, "VersionTLS10", VersionTLS10, "")
test(t, "VersionTLS11", VersionTLS11, "")
test(t, "VersionTLS12", VersionTLS12, "")
test(t, "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")
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)
})
})
}

View 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

View 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

View 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)

View file

@ -307,6 +307,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
return err
}
c.didHRR = true
return nil
}

View 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},
},
}

View 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
}

View 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
}

View file

@ -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{.|