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