| // 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 "slices" |
| |
| func addChangeCipherSpecTests() { |
| // Test missing ChangeCipherSpecs. |
| testCases = append(testCases, testCase{ |
| name: "SkipChangeCipherSpec-Client", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| SkipChangeCipherSpec: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "SkipChangeCipherSpec-Server", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| SkipChangeCipherSpec: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "SkipChangeCipherSpec-Server-NPN", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| NextProtos: []string{"bar"}, |
| Bugs: ProtocolBugs{ |
| SkipChangeCipherSpec: true, |
| }, |
| }, |
| flags: []string{ |
| "-advertise-npn", "\x03foo\x03bar\x03baz", |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| |
| // Test synchronization between the handshake and ChangeCipherSpec. |
| // Partial post-CCS handshake messages before ChangeCipherSpec should be |
| // rejected. Test both with and without handshake packing to handle both |
| // when the partial post-CCS message is in its own record and when it is |
| // attached to the pre-CCS message. |
| for _, packed := range []bool{false, true} { |
| var suffix string |
| if packed { |
| suffix = "-Packed" |
| } |
| |
| testCases = append(testCases, testCase{ |
| name: "FragmentAcrossChangeCipherSpec-Client" + suffix, |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| FragmentAcrossChangeCipherSpec: true, |
| PackHandshakeFlight: packed, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| name: "FragmentAcrossChangeCipherSpec-Client-Resume" + suffix, |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| }, |
| resumeSession: true, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| FragmentAcrossChangeCipherSpec: true, |
| PackHandshakeFlight: packed, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "FragmentAcrossChangeCipherSpec-Server" + suffix, |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| FragmentAcrossChangeCipherSpec: true, |
| PackHandshakeFlight: packed, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "FragmentAcrossChangeCipherSpec-Server-Resume" + suffix, |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| }, |
| resumeSession: true, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| FragmentAcrossChangeCipherSpec: true, |
| PackHandshakeFlight: packed, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "FragmentAcrossChangeCipherSpec-Server-NPN" + suffix, |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| NextProtos: []string{"bar"}, |
| Bugs: ProtocolBugs{ |
| FragmentAcrossChangeCipherSpec: true, |
| PackHandshakeFlight: packed, |
| }, |
| }, |
| flags: []string{ |
| "-advertise-npn", "\x03foo\x03bar\x03baz", |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| } |
| |
| // In TLS 1.2 resumptions, the client sends ClientHello in the first flight |
| // and ChangeCipherSpec + Finished in the second flight. Test the server's |
| // behavior when the Finished message is fragmented across not only |
| // ChangeCipherSpec but also the flight boundary. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "PartialClientFinishedWithClientHello-TLS12-Resume", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| PartialClientFinishedWithClientHello: true, |
| }, |
| }, |
| resumeSession: true, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| expectedLocalError: "remote error: unexpected message", |
| }) |
| |
| // In TLS 1.2 full handshakes without tickets, the server's first flight ends |
| // with ServerHelloDone and the second flight is ChangeCipherSpec + Finished. |
| // Test the client's behavior when the Finished message is fragmented across |
| // not only ChangeCipherSpec but also the flight boundary. |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "PartialFinishedWithServerHelloDone", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| SessionTicketsDisabled: true, |
| Bugs: ProtocolBugs{ |
| PartialFinishedWithServerHelloDone: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| expectedLocalError: "remote error: unexpected message", |
| }) |
| |
| // Test that, in DTLS 1.2, key changes are not allowed when there are |
| // buffered messages. Do this sending all messages in reverse, so that later |
| // ones are buffered, and leaving Finished unencrypted. |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| testType: serverTest, |
| name: "KeyChangeWithBufferedMessages-DTLS", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| next = slices.Clone(next) |
| slices.Reverse(next) |
| for i := range next { |
| next[i].Epoch = 0 |
| } |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| }) |
| |
| // Test synchronization between encryption changes and the handshake in |
| // TLS 1.3, where ChangeCipherSpec is implicit. |
| testCases = append(testCases, testCase{ |
| name: "PartialEncryptedExtensionsWithServerHello", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| PartialEncryptedExtensionsWithServerHello: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "PartialClientFinishedWithClientHello", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| PartialClientFinishedWithClientHello: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "PartialClientFinishedWithSecondClientHello", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| // Trigger a curve-based HelloRetryRequest. |
| DefaultCurves: []CurveID{}, |
| Bugs: ProtocolBugs{ |
| PartialClientFinishedWithSecondClientHello: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "PartialEndOfEarlyDataWithClientHello", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| PartialEndOfEarlyDataWithClientHello: true, |
| }, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| shouldFail: true, |
| expectedError: ":EXCESS_HANDSHAKE_DATA:", |
| }) |
| |
| // Test that early ChangeCipherSpecs are handled correctly. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "EarlyChangeCipherSpec-server-1", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| EarlyChangeCipherSpec: 1, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "EarlyChangeCipherSpec-server-2", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| EarlyChangeCipherSpec: 2, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_RECORD:", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "StrayChangeCipherSpec", |
| config: Config{ |
| // TODO(davidben): Once DTLS 1.3 exists, test |
| // that stray ChangeCipherSpec messages are |
| // rejected. |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { |
| c.WriteFragments([]DTLSFragment{{IsChangeCipherSpec: true, Data: []byte{1}}}) |
| c.WriteFlight(next) |
| }, |
| }, |
| }, |
| }) |
| |
| // Test that the contents of ChangeCipherSpec are checked. |
| testCases = append(testCases, testCase{ |
| name: "BadChangeCipherSpec-1", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| BadChangeCipherSpec: []byte{2}, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":BAD_CHANGE_CIPHER_SPEC:", |
| }) |
| testCases = append(testCases, testCase{ |
| name: "BadChangeCipherSpec-2", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| BadChangeCipherSpec: []byte{1, 1}, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":BAD_CHANGE_CIPHER_SPEC:", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "BadChangeCipherSpec-DTLS-1", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| BadChangeCipherSpec: []byte{2}, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":BAD_CHANGE_CIPHER_SPEC:", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: dtls, |
| name: "BadChangeCipherSpec-DTLS-2", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| BadChangeCipherSpec: []byte{1, 1}, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":BAD_CHANGE_CIPHER_SPEC:", |
| }) |
| } |