// 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"}, }) } } }