1// Copyright 2025 The BoringSSL Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package runner 16 17import "slices" 18 19func addChangeCipherSpecTests() { 20 // Test missing ChangeCipherSpecs. 21 testCases = append(testCases, testCase{ 22 name: "SkipChangeCipherSpec-Client", 23 config: Config{ 24 MaxVersion: VersionTLS12, 25 Bugs: ProtocolBugs{ 26 SkipChangeCipherSpec: true, 27 }, 28 }, 29 shouldFail: true, 30 expectedError: ":UNEXPECTED_RECORD:", 31 }) 32 testCases = append(testCases, testCase{ 33 testType: serverTest, 34 name: "SkipChangeCipherSpec-Server", 35 config: Config{ 36 MaxVersion: VersionTLS12, 37 Bugs: ProtocolBugs{ 38 SkipChangeCipherSpec: true, 39 }, 40 }, 41 shouldFail: true, 42 expectedError: ":UNEXPECTED_RECORD:", 43 }) 44 testCases = append(testCases, testCase{ 45 testType: serverTest, 46 name: "SkipChangeCipherSpec-Server-NPN", 47 config: Config{ 48 MaxVersion: VersionTLS12, 49 NextProtos: []string{"bar"}, 50 Bugs: ProtocolBugs{ 51 SkipChangeCipherSpec: true, 52 }, 53 }, 54 flags: []string{ 55 "-advertise-npn", "\x03foo\x03bar\x03baz", 56 }, 57 shouldFail: true, 58 expectedError: ":UNEXPECTED_RECORD:", 59 }) 60 61 // Test synchronization between the handshake and ChangeCipherSpec. 62 // Partial post-CCS handshake messages before ChangeCipherSpec should be 63 // rejected. Test both with and without handshake packing to handle both 64 // when the partial post-CCS message is in its own record and when it is 65 // attached to the pre-CCS message. 66 for _, packed := range []bool{false, true} { 67 var suffix string 68 if packed { 69 suffix = "-Packed" 70 } 71 72 testCases = append(testCases, testCase{ 73 name: "FragmentAcrossChangeCipherSpec-Client" + suffix, 74 config: Config{ 75 MaxVersion: VersionTLS12, 76 Bugs: ProtocolBugs{ 77 FragmentAcrossChangeCipherSpec: true, 78 PackHandshakeFlight: packed, 79 }, 80 }, 81 shouldFail: true, 82 expectedError: ":UNEXPECTED_RECORD:", 83 }) 84 testCases = append(testCases, testCase{ 85 name: "FragmentAcrossChangeCipherSpec-Client-Resume" + suffix, 86 config: Config{ 87 MaxVersion: VersionTLS12, 88 }, 89 resumeSession: true, 90 resumeConfig: &Config{ 91 MaxVersion: VersionTLS12, 92 Bugs: ProtocolBugs{ 93 FragmentAcrossChangeCipherSpec: true, 94 PackHandshakeFlight: packed, 95 }, 96 }, 97 shouldFail: true, 98 expectedError: ":UNEXPECTED_RECORD:", 99 }) 100 testCases = append(testCases, testCase{ 101 testType: serverTest, 102 name: "FragmentAcrossChangeCipherSpec-Server" + suffix, 103 config: Config{ 104 MaxVersion: VersionTLS12, 105 Bugs: ProtocolBugs{ 106 FragmentAcrossChangeCipherSpec: true, 107 PackHandshakeFlight: packed, 108 }, 109 }, 110 shouldFail: true, 111 expectedError: ":UNEXPECTED_RECORD:", 112 }) 113 testCases = append(testCases, testCase{ 114 testType: serverTest, 115 name: "FragmentAcrossChangeCipherSpec-Server-Resume" + suffix, 116 config: Config{ 117 MaxVersion: VersionTLS12, 118 }, 119 resumeSession: true, 120 resumeConfig: &Config{ 121 MaxVersion: VersionTLS12, 122 Bugs: ProtocolBugs{ 123 FragmentAcrossChangeCipherSpec: true, 124 PackHandshakeFlight: packed, 125 }, 126 }, 127 shouldFail: true, 128 expectedError: ":UNEXPECTED_RECORD:", 129 }) 130 testCases = append(testCases, testCase{ 131 testType: serverTest, 132 name: "FragmentAcrossChangeCipherSpec-Server-NPN" + suffix, 133 config: Config{ 134 MaxVersion: VersionTLS12, 135 NextProtos: []string{"bar"}, 136 Bugs: ProtocolBugs{ 137 FragmentAcrossChangeCipherSpec: true, 138 PackHandshakeFlight: packed, 139 }, 140 }, 141 flags: []string{ 142 "-advertise-npn", "\x03foo\x03bar\x03baz", 143 }, 144 shouldFail: true, 145 expectedError: ":UNEXPECTED_RECORD:", 146 }) 147 } 148 149 // In TLS 1.2 resumptions, the client sends ClientHello in the first flight 150 // and ChangeCipherSpec + Finished in the second flight. Test the server's 151 // behavior when the Finished message is fragmented across not only 152 // ChangeCipherSpec but also the flight boundary. 153 testCases = append(testCases, testCase{ 154 testType: serverTest, 155 name: "PartialClientFinishedWithClientHello-TLS12-Resume", 156 config: Config{ 157 MaxVersion: VersionTLS12, 158 }, 159 resumeConfig: &Config{ 160 MaxVersion: VersionTLS12, 161 Bugs: ProtocolBugs{ 162 PartialClientFinishedWithClientHello: true, 163 }, 164 }, 165 resumeSession: true, 166 shouldFail: true, 167 expectedError: ":EXCESS_HANDSHAKE_DATA:", 168 expectedLocalError: "remote error: unexpected message", 169 }) 170 171 // In TLS 1.2 full handshakes without tickets, the server's first flight ends 172 // with ServerHelloDone and the second flight is ChangeCipherSpec + Finished. 173 // Test the client's behavior when the Finished message is fragmented across 174 // not only ChangeCipherSpec but also the flight boundary. 175 testCases = append(testCases, testCase{ 176 testType: clientTest, 177 name: "PartialFinishedWithServerHelloDone", 178 config: Config{ 179 MaxVersion: VersionTLS12, 180 SessionTicketsDisabled: true, 181 Bugs: ProtocolBugs{ 182 PartialFinishedWithServerHelloDone: true, 183 }, 184 }, 185 shouldFail: true, 186 expectedError: ":EXCESS_HANDSHAKE_DATA:", 187 expectedLocalError: "remote error: unexpected message", 188 }) 189 190 // Test that, in DTLS 1.2, key changes are not allowed when there are 191 // buffered messages. Do this sending all messages in reverse, so that later 192 // ones are buffered, and leaving Finished unencrypted. 193 testCases = append(testCases, testCase{ 194 protocol: dtls, 195 testType: serverTest, 196 name: "KeyChangeWithBufferedMessages-DTLS", 197 config: Config{ 198 MaxVersion: VersionTLS12, 199 Bugs: ProtocolBugs{ 200 WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { 201 next = slices.Clone(next) 202 slices.Reverse(next) 203 for i := range next { 204 next[i].Epoch = 0 205 } 206 c.WriteFlight(next) 207 }, 208 }, 209 }, 210 shouldFail: true, 211 expectedError: ":EXCESS_HANDSHAKE_DATA:", 212 }) 213 214 // Test synchronization between encryption changes and the handshake in 215 // TLS 1.3, where ChangeCipherSpec is implicit. 216 testCases = append(testCases, testCase{ 217 name: "PartialEncryptedExtensionsWithServerHello", 218 config: Config{ 219 MaxVersion: VersionTLS13, 220 Bugs: ProtocolBugs{ 221 PartialEncryptedExtensionsWithServerHello: true, 222 }, 223 }, 224 shouldFail: true, 225 expectedError: ":EXCESS_HANDSHAKE_DATA:", 226 }) 227 testCases = append(testCases, testCase{ 228 testType: serverTest, 229 name: "PartialClientFinishedWithClientHello", 230 config: Config{ 231 MaxVersion: VersionTLS13, 232 Bugs: ProtocolBugs{ 233 PartialClientFinishedWithClientHello: true, 234 }, 235 }, 236 shouldFail: true, 237 expectedError: ":EXCESS_HANDSHAKE_DATA:", 238 }) 239 testCases = append(testCases, testCase{ 240 testType: serverTest, 241 name: "PartialClientFinishedWithSecondClientHello", 242 config: Config{ 243 MaxVersion: VersionTLS13, 244 // Trigger a curve-based HelloRetryRequest. 245 DefaultCurves: []CurveID{}, 246 Bugs: ProtocolBugs{ 247 PartialClientFinishedWithSecondClientHello: true, 248 }, 249 }, 250 shouldFail: true, 251 expectedError: ":EXCESS_HANDSHAKE_DATA:", 252 }) 253 testCases = append(testCases, testCase{ 254 testType: serverTest, 255 name: "PartialEndOfEarlyDataWithClientHello", 256 config: Config{ 257 MaxVersion: VersionTLS13, 258 }, 259 resumeConfig: &Config{ 260 MaxVersion: VersionTLS13, 261 Bugs: ProtocolBugs{ 262 PartialEndOfEarlyDataWithClientHello: true, 263 }, 264 }, 265 resumeSession: true, 266 earlyData: true, 267 shouldFail: true, 268 expectedError: ":EXCESS_HANDSHAKE_DATA:", 269 }) 270 271 // Test that early ChangeCipherSpecs are handled correctly. 272 testCases = append(testCases, testCase{ 273 testType: serverTest, 274 name: "EarlyChangeCipherSpec-server-1", 275 config: Config{ 276 MaxVersion: VersionTLS12, 277 Bugs: ProtocolBugs{ 278 EarlyChangeCipherSpec: 1, 279 }, 280 }, 281 shouldFail: true, 282 expectedError: ":UNEXPECTED_RECORD:", 283 }) 284 testCases = append(testCases, testCase{ 285 testType: serverTest, 286 name: "EarlyChangeCipherSpec-server-2", 287 config: Config{ 288 MaxVersion: VersionTLS12, 289 Bugs: ProtocolBugs{ 290 EarlyChangeCipherSpec: 2, 291 }, 292 }, 293 shouldFail: true, 294 expectedError: ":UNEXPECTED_RECORD:", 295 }) 296 testCases = append(testCases, testCase{ 297 protocol: dtls, 298 name: "StrayChangeCipherSpec", 299 config: Config{ 300 // TODO(davidben): Once DTLS 1.3 exists, test 301 // that stray ChangeCipherSpec messages are 302 // rejected. 303 MaxVersion: VersionTLS12, 304 Bugs: ProtocolBugs{ 305 WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) { 306 c.WriteFragments([]DTLSFragment{{IsChangeCipherSpec: true, Data: []byte{1}}}) 307 c.WriteFlight(next) 308 }, 309 }, 310 }, 311 }) 312 313 // Test that the contents of ChangeCipherSpec are checked. 314 testCases = append(testCases, testCase{ 315 name: "BadChangeCipherSpec-1", 316 config: Config{ 317 MaxVersion: VersionTLS12, 318 Bugs: ProtocolBugs{ 319 BadChangeCipherSpec: []byte{2}, 320 }, 321 }, 322 shouldFail: true, 323 expectedError: ":BAD_CHANGE_CIPHER_SPEC:", 324 }) 325 testCases = append(testCases, testCase{ 326 name: "BadChangeCipherSpec-2", 327 config: Config{ 328 MaxVersion: VersionTLS12, 329 Bugs: ProtocolBugs{ 330 BadChangeCipherSpec: []byte{1, 1}, 331 }, 332 }, 333 shouldFail: true, 334 expectedError: ":BAD_CHANGE_CIPHER_SPEC:", 335 }) 336 testCases = append(testCases, testCase{ 337 protocol: dtls, 338 name: "BadChangeCipherSpec-DTLS-1", 339 config: Config{ 340 MaxVersion: VersionTLS12, 341 Bugs: ProtocolBugs{ 342 BadChangeCipherSpec: []byte{2}, 343 }, 344 }, 345 shouldFail: true, 346 expectedError: ":BAD_CHANGE_CIPHER_SPEC:", 347 }) 348 testCases = append(testCases, testCase{ 349 protocol: dtls, 350 name: "BadChangeCipherSpec-DTLS-2", 351 config: Config{ 352 MaxVersion: VersionTLS12, 353 Bugs: ProtocolBugs{ 354 BadChangeCipherSpec: []byte{1, 1}, 355 }, 356 }, 357 shouldFail: true, 358 expectedError: ":BAD_CHANGE_CIPHER_SPEC:", 359 }) 360} 361