Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
1082 lines
31 KiB
Go
1082 lines
31 KiB
Go
// 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"},
|
|
})
|
|
}
|
|
}
|
|
}
|