blob: 95d0e7858bfcbc852e5f80a4cb882d003a3bcad8 [file] [log] [blame]
// 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
func addVersionNegotiationTests() {
for _, protocol := range []protocol{tls, dtls, quic} {
for _, shimVers := range allVersions(protocol) {
// Assemble flags to disable all newer versions on the shim.
var flags []string
for _, vers := range allVersions(protocol) {
if vers.version > shimVers.version {
flags = append(flags, vers.excludeFlag)
}
}
flags2 := []string{"-max-version", shimVers.shimFlag(protocol)}
// Test configuring the runner's maximum version.
for _, runnerVers := range allVersions(protocol) {
expectedVersion := shimVers.version
if runnerVers.version < shimVers.version {
expectedVersion = runnerVers.version
}
suffix := shimVers.name + "-" + runnerVers.name
suffix += "-" + protocol.String()
// Determine the expected initial record-layer versions.
clientVers := shimVers.version
if clientVers > VersionTLS10 {
clientVers = VersionTLS10
}
clientVers = recordVersionToWire(clientVers, protocol)
serverVers := expectedVersion
if expectedVersion >= VersionTLS13 {
serverVers = VersionTLS12
}
serverVers = recordVersionToWire(serverVers, protocol)
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "VersionNegotiation-Client-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: clientVers,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "VersionNegotiation-Client2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: clientVers,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiation-Server-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: serverVers,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiation-Server2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: serverVers,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
})
}
}
}
// Test the version extension at all versions.
for _, protocol := range []protocol{tls, dtls, quic} {
for _, vers := range allVersions(protocol) {
suffix := vers.name + "-" + protocol.String()
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiationExtension-" + suffix,
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111, vers.wire(protocol), 0x2222},
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: vers.version,
},
})
}
}
// If all versions are unknown, negotiation fails.
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSupportedVersions",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111},
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "NoSupportedVersions-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111},
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ClientHelloVersionTooHigh",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendClientVersion: 0x0304,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ConflictingVersionNegotiation",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: VersionTLS12,
SendSupportedVersions: []uint16{VersionTLS11},
IgnoreTLS13DowngradeRandom: true,
},
},
// The extension takes precedence over the ClientHello version.
expectations: connectionExpectations{
version: VersionTLS11,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ConflictingVersionNegotiation-2",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: VersionTLS11,
SendSupportedVersions: []uint16{VersionTLS12},
IgnoreTLS13DowngradeRandom: true,
},
},
// The extension takes precedence over the ClientHello version.
expectations: connectionExpectations{
version: VersionTLS12,
},
})
// Test that TLS 1.2 isn't negotiated by the supported_versions extension in
// the ServerHello.
testCases = append(testCases, testCase{
testType: clientTest,
name: "SupportedVersionSelection-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendServerSupportedVersionExtension: VersionTLS12,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
// Test that the maximum version is selected regardless of the
// client-sent order.
testCases = append(testCases, testCase{
testType: serverTest,
name: "IgnoreClientVersionOrder",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{VersionTLS12, VersionTLS13},
},
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
// Test for version tolerance.
testCases = append(testCases, testCase{
testType: serverTest,
name: "MinorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x03ff,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "MajorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0400,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
// TLS 1.3 must be negotiated with the supported_versions
// extension, not ClientHello.version.
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "VersionTolerance-TLS13",
config: Config{
Bugs: ProtocolBugs{
// Although TLS 1.3 does not use
// ClientHello.version, it still tolerates high
// values there.
SendClientVersion: 0x0400,
},
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "MinorVersionTolerance-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xfe00,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "MajorVersionTolerance-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xfdff,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
// Test that versions below 3.0 are rejected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "VersionTooLow",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0200,
OmitSupportedVersions: true,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "VersionTooLow-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xffff,
OmitSupportedVersions: true,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
name: "ServerBogusVersion",
config: Config{
Bugs: ProtocolBugs{
SendServerHelloVersion: 0x1234,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
// Test TLS 1.3's downgrade signal.
for _, protocol := range []protocol{tls, dtls} {
for _, vers := range allVersions(protocol) {
if vers.version >= VersionTLS13 {
continue
}
clientShimError := "tls: downgrade from TLS 1.3 detected"
if vers.version < VersionTLS12 {
clientShimError = "tls: downgrade from TLS 1.2 detected"
}
// for _, test := range downgradeTests {
// The client should enforce the downgrade sentinel.
testCases = append(testCases, testCase{
protocol: protocol,
name: "Downgrade-" + vers.name + "-Client-" + protocol.String(),
config: Config{
Bugs: ProtocolBugs{
NegotiateVersion: vers.wire(protocol),
},
},
expectations: connectionExpectations{
version: vers.version,
},
shouldFail: true,
expectedError: ":TLS13_DOWNGRADE:",
expectedLocalError: "remote error: illegal parameter",
})
// The server should emit the downgrade signal.
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Downgrade-" + vers.name + "-Server-" + protocol.String(),
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{vers.wire(protocol)},
},
},
expectations: connectionExpectations{
version: vers.version,
},
shouldFail: true,
expectedLocalError: clientShimError,
})
}
}
// SSL 3.0 support has been removed. Test that the shim does not
// support it.
testCases = append(testCases, testCase{
name: "NoSSL3-Client",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
},
shouldFail: true,
expectedLocalError: "tls: client did not offer any supported protocol versions",
})
testCases = append(testCases, testCase{
name: "NoSSL3-Client-Unsolicited",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
Bugs: ProtocolBugs{
// The above test asserts the client does not
// offer SSL 3.0 in the supported_versions
// list. Additionally assert that it rejects an
// unsolicited SSL 3.0 ServerHello.
NegotiateVersion: VersionSSL30,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
expectedLocalError: "remote error: protocol version not supported",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSSL3-Server",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
expectedLocalError: "remote error: protocol version not supported",
})
}
func addMinimumVersionTests() {
for _, protocol := range []protocol{tls, dtls, quic} {
for _, shimVers := range allVersions(protocol) {
// Assemble flags to disable all older versions on the shim.
var flags []string
for _, vers := range allVersions(protocol) {
if vers.version < shimVers.version {
flags = append(flags, vers.excludeFlag)
}
}
flags2 := []string{"-min-version", shimVers.shimFlag(protocol)}
for _, runnerVers := range allVersions(protocol) {
suffix := shimVers.name + "-" + runnerVers.name
suffix += "-" + protocol.String()
var expectedVersion uint16
var shouldFail bool
var expectedError, expectedLocalError string
if runnerVers.version >= shimVers.version {
expectedVersion = runnerVers.version
} else {
shouldFail = true
expectedError = ":UNSUPPORTED_PROTOCOL:"
expectedLocalError = "remote error: protocol version not supported"
}
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "MinimumVersion-Client-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
// Ensure the server does not decline to
// select a version (versions extension) or
// cipher (some ciphers depend on versions).
NegotiateVersion: runnerVers.wire(protocol),
IgnorePeerCipherPreferences: shouldFail,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "MinimumVersion-Client2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
// Ensure the server does not decline to
// select a version (versions extension) or
// cipher (some ciphers depend on versions).
NegotiateVersion: runnerVers.wire(protocol),
IgnorePeerCipherPreferences: shouldFail,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "MinimumVersion-Server-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "MinimumVersion-Server2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
})
}
}
}
}
func addRecordVersionTests() {
for _, ver := range tlsVersions {
// Test that the record version is enforced.
testCases = append(testCases, testCase{
name: "CheckRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendRecordVersion: 0x03ff,
},
},
shouldFail: true,
expectedError: ":WRONG_VERSION_NUMBER:",
})
// Test that the ClientHello may use any record version, for
// compatibility reasons.
testCases = append(testCases, testCase{
testType: serverTest,
name: "LooseInitialRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendInitialRecordVersion: 0x03ff,
},
},
})
// Test that garbage ClientHello record versions are rejected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "GarbageInitialRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendInitialRecordVersion: 0xffff,
},
},
shouldFail: true,
expectedError: ":WRONG_VERSION_NUMBER:",
})
}
}