|  | // Copyright 2025 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package runner | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | func addResumptionVersionTests() { | 
|  | for _, sessionVers := range tlsVersions { | 
|  | for _, resumeVers := range tlsVersions { | 
|  | protocols := []protocol{tls} | 
|  | if sessionVers.hasDTLS && resumeVers.hasDTLS { | 
|  | protocols = append(protocols, dtls) | 
|  | } | 
|  | if sessionVers.hasQUIC && resumeVers.hasQUIC { | 
|  | protocols = append(protocols, quic) | 
|  | } | 
|  | for _, protocol := range protocols { | 
|  | suffix := "-" + sessionVers.name + "-" + resumeVers.name | 
|  | suffix += "-" + protocol.String() | 
|  |  | 
|  | if sessionVers.version == resumeVers.version { | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | name:          "Resume-Client" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: sessionVers.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNoTLS13PSK: sessionVers.version < VersionTLS13, | 
|  | }, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | }) | 
|  | } else if protocol != tls && sessionVers.version >= VersionTLS13 && resumeVers.version < VersionTLS13 { | 
|  | // In TLS 1.2 and below, the server indicates resumption by echoing | 
|  | // the client's session ID, which is impossible if the client did | 
|  | // not send a session ID. If the client offers a TLS 1.3 session, it | 
|  | // only fills in session ID in TLS (not DTLS or QUIC) for middlebox | 
|  | // compatibility mode. So, instead, test that the session ID was | 
|  | // empty and it was indeed impossible to hit this path | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | name:          "Resume-Client-Impossible" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: sessionVers.version, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: resumeVers.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNoSessionID: true, | 
|  | }, | 
|  | }, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  | } else { | 
|  | // Test that the client rejects ServerHellos which resume | 
|  | // sessions at inconsistent versions. | 
|  | expectedError := ":OLD_SESSION_VERSION_NOT_RETURNED:" | 
|  | if sessionVers.version < VersionTLS13 && resumeVers.version >= VersionTLS13 { | 
|  | // The server will "resume" the session by sending pre_shared_key, | 
|  | // but the shim will not have sent pre_shared_key at all. The shim | 
|  | // should reject this because the extension was not allowed at all. | 
|  | expectedError = ":UNEXPECTED_EXTENSION:" | 
|  | } | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | name:          "Resume-Client-Mismatch" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: sessionVers.version, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: resumeVers.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | AcceptAnySession: true, | 
|  | }, | 
|  | }, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: expectedError, | 
|  | }) | 
|  | } | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | name:          "Resume-Client-NoResume" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: sessionVers.version, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: resumeVers.version, | 
|  | }, | 
|  | newSessionsOnResume:  true, | 
|  | expectResumeRejected: true, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: sessionVers.version, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | expectResumeRejected: sessionVers != resumeVers, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: resumeVers.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendBothTickets: true, | 
|  | }, | 
|  | }, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Repeat the test using session IDs, rather than tickets. | 
|  | if sessionVers.version < VersionTLS13 && resumeVers.version < VersionTLS13 { | 
|  | testCases = append(testCases, testCase{ | 
|  | protocol:      protocol, | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-NoTickets" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:             sessionVers.version, | 
|  | SessionTicketsDisabled: true, | 
|  | }, | 
|  | expectations: connectionExpectations{ | 
|  | version: sessionVers.version, | 
|  | }, | 
|  | expectResumeRejected: sessionVers != resumeVers, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:             resumeVers.version, | 
|  | SessionTicketsDisabled: true, | 
|  | }, | 
|  | resumeExpectations: &connectionExpectations{ | 
|  | version: resumeVers.version, | 
|  | }, | 
|  | }) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Make sure shim ticket mutations are functional. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "ShimTicketRewritable", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | in, err := SetShimTicketVersion(in, VersionTLS12) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Resumptions are declined if the version does not match. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-DeclineCrossVersion", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNewTicket: true, | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketVersion(in, VersionTLS13) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-DeclineCrossVersion-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketVersion(in, VersionTLS12) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // Resumptions are declined if the cipher is invalid or disabled. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-DeclineBadCipher", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNewTicket: true, | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-DeclineBadCipher-2", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNewTicket: true, | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-cipher", "AES128", | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // Sessions are not resumed if they do not use the preferred cipher. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-CipherNotPreferred", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNewTicket: true, | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | shouldFail:           false, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // TLS 1.3 allows sessions to be resumed at a different cipher if their | 
|  | // PRF hashes match, but BoringSSL will always decline such resumptions. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-CipherNotPreferred-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | // If the client (runner) offers ChaCha20-Poly1305 first, the | 
|  | // server (shim) always prefers it. Switch it to AES-GCM. | 
|  | return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | shouldFail:           false, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // Sessions may not be resumed if they contain another version's cipher. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-DeclineBadCipher-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | FilterTicket: func(in []byte) ([]byte, error) { | 
|  | return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-ticket-key", | 
|  | base64FlagValue(TestShimTicketKey), | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // If the client does not offer the cipher from the session, decline to | 
|  | // resume. Clients are forbidden from doing this, but BoringSSL selects | 
|  | // the cipher first, so we only decline. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-UnofferedCipher", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | }, | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // In TLS 1.3, clients may advertise a cipher list which does not | 
|  | // include the selected cipher. Test that we tolerate this. Servers may | 
|  | // resume at another cipher if the PRF matches and are not doing 0-RTT, but | 
|  | // BoringSSL will always decline. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-UnofferedCipher-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, | 
|  | }, | 
|  | }, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // Sessions may not be resumed at a different cipher. | 
|  | testCases = append(testCases, testCase{ | 
|  | name:          "Resume-Client-CipherMismatch", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:   VersionTLS12, | 
|  | CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCipherSuite: TLS_RSA_WITH_AES_128_CBC_SHA, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:", | 
|  | }) | 
|  |  | 
|  | // Session resumption in TLS 1.3 may change the cipher suite if the PRF | 
|  | // matches. | 
|  | testCases = append(testCases, testCase{ | 
|  | name:          "Resume-Client-CipherMismatch-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256}, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Session resumption in TLS 1.3 is forbidden if the PRF does not match. | 
|  | testCases = append(testCases, testCase{ | 
|  | name:          "Resume-Client-PRFMismatch-TLS13", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion:   VersionTLS13, | 
|  | CipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendCipherSuite: TLS_AES_256_GCM_SHA384, | 
|  | }, | 
|  | }, | 
|  | shouldFail:    true, | 
|  | expectedError: ":OLD_SESSION_PRF_HASH_MISMATCH:", | 
|  | }) | 
|  |  | 
|  | for _, secondBinder := range []bool{false, true} { | 
|  | var suffix string | 
|  | var defaultCurves []CurveID | 
|  | if secondBinder { | 
|  | suffix = "-SecondBinder" | 
|  | // Force a HelloRetryRequest by predicting an empty curve list. | 
|  | defaultCurves = []CurveID{} | 
|  | } | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-BinderWrongLength" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendShortPSKBinder:         true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: error decrypting message", | 
|  | expectedError:      ":DIGEST_CHECK_FAILED:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-NoPSKBinder" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendNoPSKBinder:            true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: error decoding message", | 
|  | expectedError:      ":DECODE_ERROR:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-ExtraPSKBinder" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendExtraPSKBinder:         true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | expectedError:      ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-ExtraIdentityNoBinder" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExtraPSKIdentity:           true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | expectedError:      ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-InvalidPSKBinder" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendInvalidPSKBinder:       true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: error decrypting message", | 
|  | expectedError:      ":DIGEST_CHECK_FAILED:", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-PSKBinderFirstExtension" + suffix, | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: defaultCurves, | 
|  | Bugs: ProtocolBugs{ | 
|  | PSKBinderFirst:             true, | 
|  | OnlyCorruptSecondPSKBinder: secondBinder, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | expectedError:      ":PRE_SHARED_KEY_MUST_BE_LAST:", | 
|  | }) | 
|  | } | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType:      serverTest, | 
|  | name:          "Resume-Server-OmitPSKsOnSecondClientHello", | 
|  | resumeSession: true, | 
|  | config: Config{ | 
|  | MaxVersion:    VersionTLS13, | 
|  | DefaultCurves: []CurveID{}, | 
|  | Bugs: ProtocolBugs{ | 
|  | OmitPSKsOnSecondClientHello: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | expectedError:      ":INCONSISTENT_CLIENT_HELLO:", | 
|  | }) | 
|  | } | 
|  |  | 
|  | func addSessionTicketTests() { | 
|  | testCases = append(testCases, testCase{ | 
|  | // In TLS 1.2 and below, empty NewSessionTicket messages | 
|  | // mean the server changed its mind on sending a ticket. | 
|  | name: "SendEmptySessionTicket-TLS12", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS12, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendEmptySessionTicket: true, | 
|  | }, | 
|  | }, | 
|  | flags: []string{"-expect-no-session"}, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | // In TLS 1.3, empty NewSessionTicket messages are not | 
|  | // allowed. | 
|  | name: "SendEmptySessionTicket-TLS13", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendEmptySessionTicket: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":DECODE_ERROR:", | 
|  | expectedLocalError: "remote error: error decoding message", | 
|  | }) | 
|  |  | 
|  | // Test that the server ignores unknown PSK modes. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-SendUnknownModeSessionTicket-Server", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a}, | 
|  | }, | 
|  | }, | 
|  | resumeSession: true, | 
|  | expectations: connectionExpectations{ | 
|  | version: VersionTLS13, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that the server does not send session tickets with no matching key exchange mode. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-ExpectNoSessionTicketOnBadKEMode-Server", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendPSKKeyExchangeModes:  []byte{0x1a}, | 
|  | ExpectNoNewSessionTicket: true, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that the server does not accept a session with no matching key exchange mode. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-SendBadKEModeSessionTicket-Server", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendPSKKeyExchangeModes: []byte{0x1a}, | 
|  | }, | 
|  | }, | 
|  | resumeSession:        true, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | // Test that the server rejects ClientHellos with pre_shared_key but without | 
|  | // psk_key_exchange_modes. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-SendNoKEMModesWithPSK-Server", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendPSKKeyExchangeModes: []byte{}, | 
|  | }, | 
|  | }, | 
|  | resumeSession:      true, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "remote error: missing extension", | 
|  | expectedError:      ":MISSING_EXTENSION:", | 
|  | }) | 
|  |  | 
|  | // Test that the client ticket age is sent correctly. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-TestValidTicketAge-Client", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectTicketAge: 10 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession: true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "10", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that the client ticket age is enforced. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-TestBadTicketAge-Client", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectTicketAge: 1000 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession:      true, | 
|  | shouldFail:         true, | 
|  | expectedLocalError: "tls: invalid ticket age", | 
|  | }) | 
|  |  | 
|  | // Test that the server's ticket age skew reporting works. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Forward", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 15 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession:        true, | 
|  | resumeRenewedSession: true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "10", | 
|  | "-expect-ticket-age-skew", "5", | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Backward", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 5 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession:        true, | 
|  | resumeRenewedSession: true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "10", | 
|  | "-expect-ticket-age-skew", "-5", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that ticket age skew up to 60 seconds in either direction is accepted. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Forward-60-Accept", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 70 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession: true, | 
|  | earlyData:     true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "10", | 
|  | "-expect-ticket-age-skew", "60", | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Backward-60-Accept", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 10 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession: true, | 
|  | earlyData:     true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "70", | 
|  | "-expect-ticket-age-skew", "-60", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that ticket age skew beyond 60 seconds in either direction is rejected. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Forward-61-Reject", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 71 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession:           true, | 
|  | earlyData:               true, | 
|  | expectEarlyDataRejected: true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "10", | 
|  | "-expect-ticket-age-skew", "61", | 
|  | "-on-resume-expect-early-data-reason", "ticket_age_skew", | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-TicketAgeSkew-Backward-61-Reject", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketAge: 10 * time.Second, | 
|  | }, | 
|  | }, | 
|  | resumeSession:           true, | 
|  | earlyData:               true, | 
|  | expectEarlyDataRejected: true, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "71", | 
|  | "-expect-ticket-age-skew", "-61", | 
|  | "-on-resume-expect-early-data-reason", "ticket_age_skew", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-SendTicketEarlyDataSupport", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | MaxEarlyDataSize: 16384, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-enable-early-data", | 
|  | "-expect-ticket-supports-early-data", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that 0-RTT tickets are still recorded as such when early data is disabled overall. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-SendTicketEarlyDataSupport-Disabled", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | MaxEarlyDataSize: 16384, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-expect-ticket-supports-early-data", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-DuplicateTicketEarlyDataSupport", | 
|  | config: Config{ | 
|  | MaxVersion:       VersionTLS13, | 
|  | MaxEarlyDataSize: 16384, | 
|  | Bugs: ProtocolBugs{ | 
|  | DuplicateTicketEarlyData: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":DUPLICATE_EXTENSION:", | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | }) | 
|  |  | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     "TLS13-ExpectTicketEarlyDataSupport", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectTicketEarlyData: true, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-enable-early-data", | 
|  | }, | 
|  | }) | 
|  |  | 
|  | // Test that, in TLS 1.3, the server-offered NewSessionTicket lifetime | 
|  | // is honored. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-HonorServerSessionTicketLifetime", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketLifetime: 20 * time.Second, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "19", | 
|  | }, | 
|  | resumeSession: true, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: clientTest, | 
|  | name:     "TLS13-HonorServerSessionTicketLifetime-2", | 
|  | config: Config{ | 
|  | MaxVersion: VersionTLS13, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketLifetime: 20 * time.Second, | 
|  | // The client should not offer the expired session. | 
|  | ExpectNoTLS13PSK: true, | 
|  | }, | 
|  | }, | 
|  | flags: []string{ | 
|  | "-resumption-delay", "21", | 
|  | }, | 
|  | resumeSession:        true, | 
|  | expectResumeRejected: true, | 
|  | }) | 
|  |  | 
|  | for _, ver := range tlsVersions { | 
|  | // Prior to TLS 1.3, disabling session tickets enables session IDs. | 
|  | useStatefulResumption := ver.version < VersionTLS13 | 
|  |  | 
|  | // SSL_OP_NO_TICKET implies the server must not mint any tickets. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     ver.name + "-NoTicket-NoMint", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNoNewSessionTicket: true, | 
|  | RequireSessionIDs:        useStatefulResumption, | 
|  | }, | 
|  | }, | 
|  | resumeSession: useStatefulResumption, | 
|  | flags:         []string{"-no-ticket"}, | 
|  | }) | 
|  |  | 
|  | // SSL_OP_NO_TICKET implies the server must not accept any tickets. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     ver.name + "-NoTicket-NoAccept", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | }, | 
|  | resumeSession:        true, | 
|  | expectResumeRejected: true, | 
|  | // Set SSL_OP_NO_TICKET on the second connection, after the first | 
|  | // has established tickets. | 
|  | flags: []string{"-on-resume-no-ticket"}, | 
|  | }) | 
|  |  | 
|  | // SSL_OP_NO_TICKET implies the client must not offer ticket-based | 
|  | // sessions. The client not only should not send the session ticket | 
|  | // extension, but if the server echos the session ID, the client should | 
|  | // reject this. | 
|  | if ver.version < VersionTLS13 { | 
|  | testCases = append(testCases, testCase{ | 
|  | name: ver.name + "-NoTicket-NoOffer", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | }, | 
|  | resumeConfig: &Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectNoTLS12TicketSupport: true, | 
|  | // Pretend to accept the session, even though the client | 
|  | // did not offer it. The client should reject this as | 
|  | // invalid. A buggy client will still fail because it | 
|  | // expects resumption, but with a different error. | 
|  | // Ideally, we would test this by actually resuming the | 
|  | // previous session, even though the client did not | 
|  | // provide a ticket. | 
|  | EchoSessionIDInFullHandshake: true, | 
|  | }, | 
|  | }, | 
|  | resumeSession:        true, | 
|  | expectResumeRejected: true, | 
|  | // Set SSL_OP_NO_TICKET on the second connection, after the first | 
|  | // has established tickets. | 
|  | flags:              []string{"-on-resume-no-ticket"}, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":SERVER_ECHOED_INVALID_SESSION_ID:", | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Test ticket flags. | 
|  | if ver.version >= VersionTLS13 { | 
|  | // The client should parse and ignore unknown ticket flags. 2039 | 
|  | // is the highest possible flag number (8*255 flags total). | 
|  | for i, flags := range [][]uint{{1}, {31}, {100}, {2039}, {1, 31, 100, 2039}} { | 
|  | testCases = append(testCases, testCase{ | 
|  | name: fmt.Sprintf("%s-Client-UnknownTicketFlags-%d", ver.name, i), | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketFlags: flags, | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: fmt.Sprintf("%s-Client-KnownAndUnknownTicketFlags-%d", ver.name, i), | 
|  | config: Config{ | 
|  | MinVersion:            ver.version, | 
|  | MaxVersion:            ver.version, | 
|  | ResumptionAcrossNames: true, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketFlags: flags, | 
|  | }, | 
|  | }, | 
|  | flags: []string{"-expect-resumable-across-names"}, | 
|  | }) | 
|  | } | 
|  |  | 
|  | // The client should reject invalid ticket flag extensions. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: ver.name + "-Client-NonminimalTicketFlags", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | SendTicketFlags:   []uint{1}, | 
|  | TicketFlagPadding: 1, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":DECODE_ERROR:", | 
|  | expectedLocalError: "remote error: illegal parameter", | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: ver.name + "-Client-EmptyTicketFlags", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | AlwaysSendTicketFlags: true, | 
|  | }, | 
|  | }, | 
|  | shouldFail:         true, | 
|  | expectedError:      ":DECODE_ERROR:", | 
|  | expectedLocalError: "remote error: error decoding message", | 
|  | }) | 
|  |  | 
|  | // The client should parse the resumption_across_names flag. | 
|  | testCases = append(testCases, testCase{ | 
|  | name: ver.name + "-Client-NoResumptionAcrossNames", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | }, | 
|  | flags: []string{"-expect-not-resumable-across-names"}, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | name: ver.name + "-Client-ResumptionAcrossNames", | 
|  | config: Config{ | 
|  | MinVersion:            ver.version, | 
|  | MaxVersion:            ver.version, | 
|  | ResumptionAcrossNames: true, | 
|  | }, | 
|  | flags: []string{"-expect-resumable-across-names"}, | 
|  | }) | 
|  |  | 
|  | // The server should offer resumption_across_names as configured. | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     ver.name + "-Server-NoResumptionAcrossNames", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectResumptionAcrossNames: ptrTo(false), | 
|  | }, | 
|  | }, | 
|  | }) | 
|  | testCases = append(testCases, testCase{ | 
|  | testType: serverTest, | 
|  | name:     ver.name + "-Server-ResumptionAcrossNames", | 
|  | config: Config{ | 
|  | MinVersion: ver.version, | 
|  | MaxVersion: ver.version, | 
|  | Bugs: ProtocolBugs{ | 
|  | ExpectResumptionAcrossNames: ptrTo(true), | 
|  | }, | 
|  | }, | 
|  | flags: []string{"-resumption-across-names-enabled"}, | 
|  | }) | 
|  | } | 
|  | } | 
|  | } |