| // 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" |
| ) |
| |
| func addExtensionTests() { |
| exampleCertificate := rootCA.Issue(X509Info{ |
| PrivateKey: &ecdsaP256Key, |
| DNSNames: []string{"example.com"}, |
| }).ToCredential() |
| |
| // Repeat extensions tests at all versions. |
| for _, protocol := range []protocol{tls, dtls, quic} { |
| for _, ver := range allVersions(protocol) { |
| suffix := fmt.Sprintf("%s-%s", protocol.String(), ver.name) |
| |
| // Test that duplicate extensions are rejected. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "DuplicateExtensionClient-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| DuplicateExtension: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedLocalError: "remote error: error decoding message", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "DuplicateExtensionServer-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| DuplicateExtension: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedLocalError: "remote error: error decoding message", |
| }) |
| |
| // Test SNI. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "ServerNameExtensionClient-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| ExpectServerName: "example.com", |
| }, |
| Credential: &exampleCertificate, |
| }, |
| flags: []string{"-host-name", "example.com"}, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "ServerNameExtensionClientMismatch-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| ExpectServerName: "mismatch.com", |
| }, |
| }, |
| flags: []string{"-host-name", "example.com"}, |
| shouldFail: true, |
| expectedLocalError: "tls: unexpected server name", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "ServerNameExtensionClientMissing-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| ExpectServerName: "missing.com", |
| }, |
| }, |
| shouldFail: true, |
| expectedLocalError: "tls: unexpected server name", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "TolerateServerNameAck-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| SendServerNameAck: true, |
| }, |
| Credential: &exampleCertificate, |
| }, |
| flags: []string{"-host-name", "example.com"}, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: "UnsolicitedServerNameAck-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| SendServerNameAck: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "ServerNameExtensionServer-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| ServerName: "example.com", |
| }, |
| flags: []string{"-expect-server-name", "example.com"}, |
| resumeSession: true, |
| expectations: connectionExpectations{ |
| serverNameAck: ptrTo(true), |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "ServerNameExtensionServer-NoACK-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| ServerName: "example.com", |
| }, |
| flags: []string{ |
| "-expect-server-name", "example.com", |
| "-no-server-name-ack", |
| }, |
| resumeSession: true, |
| expectations: connectionExpectations{ |
| serverNameAck: ptrTo(false), |
| }, |
| }) |
| |
| // Test ALPN. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNClient-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo"}, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo\x03bar\x03baz", |
| "-expect-alpn", "foo", |
| }, |
| expectations: connectionExpectations{ |
| nextProto: "foo", |
| nextProtoType: alpn, |
| }, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNClient-RejectUnknown-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| SendALPN: "baz", |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo\x03bar", |
| }, |
| shouldFail: true, |
| expectedError: ":INVALID_ALPN_PROTOCOL:", |
| expectedLocalError: "remote error: illegal parameter", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNClient-AllowUnknown-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| SendALPN: "baz", |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo\x03bar", |
| "-allow-unknown-alpn-protos", |
| "-expect-alpn", "baz", |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| }, |
| flags: []string{ |
| "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", |
| "-select-alpn", "foo", |
| }, |
| expectations: connectionExpectations{ |
| nextProto: "foo", |
| nextProtoType: alpn, |
| }, |
| resumeSession: true, |
| }) |
| |
| var shouldDeclineALPNFail bool |
| var declineALPNError, declineALPNLocalError string |
| if protocol == quic { |
| // ALPN is mandatory in QUIC. |
| shouldDeclineALPNFail = true |
| declineALPNError = ":NO_APPLICATION_PROTOCOL:" |
| declineALPNLocalError = "remote error: no application protocol" |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-Decline-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| }, |
| flags: []string{"-decline-alpn"}, |
| expectations: connectionExpectations{ |
| noNextProto: true, |
| }, |
| resumeSession: true, |
| shouldFail: shouldDeclineALPNFail, |
| expectedError: declineALPNError, |
| expectedLocalError: declineALPNLocalError, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-Reject-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| }, |
| flags: []string{"-reject-alpn"}, |
| shouldFail: true, |
| expectedError: ":NO_APPLICATION_PROTOCOL:", |
| expectedLocalError: "remote error: no application protocol", |
| }) |
| |
| // Test that the server implementation catches itself if the |
| // callback tries to return an invalid empty ALPN protocol. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-SelectEmpty-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| }, |
| flags: []string{ |
| "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", |
| "-select-empty-alpn", |
| }, |
| shouldFail: true, |
| expectedLocalError: "remote error: internal error", |
| expectedError: ":INVALID_ALPN_PROTOCOL:", |
| }) |
| |
| // Test ALPN in async mode as well to ensure that extensions callbacks are only |
| // called once. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-Async-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| // Prior to TLS 1.3, exercise the asynchronous session callback. |
| SessionTicketsDisabled: ver.version < VersionTLS13, |
| }, |
| flags: []string{ |
| "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", |
| "-select-alpn", "foo", |
| "-async", |
| }, |
| expectations: connectionExpectations{ |
| nextProto: "foo", |
| nextProtoType: alpn, |
| }, |
| resumeSession: true, |
| }) |
| |
| var emptyString string |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNClient-EmptyProtocolName-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{""}, |
| Bugs: ProtocolBugs{ |
| // A server returning an empty ALPN protocol |
| // should be rejected. |
| ALPNProtocol: &emptyString, |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo", |
| }, |
| shouldFail: true, |
| expectedError: ":PARSE_TLSEXT:", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| skipQUICALPNConfig: true, |
| name: "ALPNServer-EmptyProtocolName-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| // A ClientHello containing an empty ALPN protocol |
| // should be rejected. |
| NextProtos: []string{"foo", "", "baz"}, |
| }, |
| flags: []string{ |
| "-select-alpn", "foo", |
| }, |
| shouldFail: true, |
| expectedError: ":PARSE_TLSEXT:", |
| }) |
| |
| // Test NPN and the interaction with ALPN. |
| if ver.version < VersionTLS13 && protocol == tls { |
| // Test that the server prefers ALPN over NPN. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "ALPNServer-Preferred-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| }, |
| flags: []string{ |
| "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", |
| "-select-alpn", "foo", |
| "-advertise-npn", "\x03foo\x03bar\x03baz", |
| }, |
| expectations: connectionExpectations{ |
| nextProto: "foo", |
| nextProtoType: alpn, |
| }, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "ALPNServer-Preferred-Swapped-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| Bugs: ProtocolBugs{ |
| SwapNPNAndALPN: true, |
| }, |
| }, |
| flags: []string{ |
| "-expect-advertised-alpn", "\x03foo\x03bar\x03baz", |
| "-select-alpn", "foo", |
| "-advertise-npn", "\x03foo\x03bar\x03baz", |
| }, |
| expectations: connectionExpectations{ |
| nextProto: "foo", |
| nextProtoType: alpn, |
| }, |
| resumeSession: true, |
| }) |
| |
| // Test that negotiating both NPN and ALPN is forbidden. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "NegotiateALPNAndNPN-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| Bugs: ProtocolBugs{ |
| NegotiateALPNAndNPN: true, |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo", |
| "-select-next-proto", "foo", |
| }, |
| shouldFail: true, |
| expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "NegotiateALPNAndNPN-Swapped-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo", "bar", "baz"}, |
| Bugs: ProtocolBugs{ |
| NegotiateALPNAndNPN: true, |
| SwapNPNAndALPN: true, |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo", |
| "-select-next-proto", "foo", |
| }, |
| shouldFail: true, |
| expectedError: ":NEGOTIATED_BOTH_NPN_AND_ALPN:", |
| }) |
| } |
| |
| // Test missing ALPN in QUIC |
| if protocol == quic { |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| protocol: protocol, |
| name: "Client-ALPNMissingFromConfig-" + suffix, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| }, |
| skipQUICALPNConfig: true, |
| shouldFail: true, |
| expectedError: ":NO_APPLICATION_PROTOCOL:", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| protocol: protocol, |
| name: "Client-ALPNMissing-" + suffix, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo", |
| }, |
| skipQUICALPNConfig: true, |
| shouldFail: true, |
| expectedError: ":NO_APPLICATION_PROTOCOL:", |
| expectedLocalError: "remote error: no application protocol", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: protocol, |
| name: "Server-ALPNMissing-" + suffix, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| }, |
| skipQUICALPNConfig: true, |
| shouldFail: true, |
| expectedError: ":NO_APPLICATION_PROTOCOL:", |
| expectedLocalError: "remote error: no application protocol", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: protocol, |
| name: "Server-ALPNMismatch-" + suffix, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo"}, |
| }, |
| flags: []string{ |
| "-decline-alpn", |
| }, |
| skipQUICALPNConfig: true, |
| shouldFail: true, |
| expectedError: ":NO_APPLICATION_PROTOCOL:", |
| expectedLocalError: "remote error: no application protocol", |
| }) |
| } |
| |
| // Test ALPS. |
| if ver.version >= VersionTLS13 { |
| // Test basic client with different ALPS codepoint. |
| for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} { |
| useAlpsCodepointFlag := "0" |
| if alpsCodePoint == ALPSUseCodepointNew { |
| useAlpsCodepointFlag = "1" |
| } |
| |
| flags := []string{"-alps-use-new-codepoint", useAlpsCodepointFlag} |
| expectations := connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim1"), |
| } |
| resumeExpectations := &connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim2"), |
| } |
| |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte("shim1"), |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettings: []byte("shim2"), |
| } |
| } |
| |
| flags = append(flags, |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-on-initial-application-settings", "proto,shim1", |
| "-on-initial-expect-peer-application-settings", "runner1", |
| "-on-resume-application-settings", "proto,shim2", |
| "-on-resume-expect-peer-application-settings", "runner2") |
| |
| // Test that server can negotiate ALPS, including different values |
| // on resumption. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-Basic-Client-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner1")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner2")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| expectations: expectations, |
| resumeExpectations: resumeExpectations, |
| flags: flags, |
| }) |
| |
| // Test basic server with different ALPS codepoint. |
| flags = []string{"-alps-use-new-codepoint", useAlpsCodepointFlag} |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim1"), |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim2"), |
| } |
| |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte("shim1"), |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettings: []byte("shim2"), |
| } |
| } |
| |
| flags = append(flags, |
| "-select-alpn", "proto", |
| "-on-initial-application-settings", "proto,shim1", |
| "-on-initial-expect-peer-application-settings", "runner1", |
| "-on-resume-application-settings", "proto,shim2", |
| "-on-resume-expect-peer-application-settings", "runner2") |
| |
| // Test that server can negotiate ALPS, including different values |
| // on resumption. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-Basic-Server-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner1")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner2")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| expectations: expectations, |
| resumeExpectations: resumeExpectations, |
| flags: flags, |
| }) |
| |
| // Try different ALPS codepoint for all the existing tests. |
| alpsFlags := []string{"-alps-use-new-codepoint", useAlpsCodepointFlag} |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim1"), |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim2"), |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte("shim1"), |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettings: []byte("shim2"), |
| } |
| } |
| |
| // Test that the server can defer its ALPS configuration to the ALPN |
| // selection callback. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-Basic-Server-Defer-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner1")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner2")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| expectations: expectations, |
| resumeExpectations: resumeExpectations, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-defer-alps", |
| "-on-initial-application-settings", "proto,shim1", |
| "-on-initial-expect-peer-application-settings", "runner1", |
| "-on-resume-application-settings", "proto,shim2", |
| "-on-resume-expect-peer-application-settings", "runner2", |
| }, alpsFlags...), |
| }) |
| |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: []byte{}, |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte{}, |
| } |
| } |
| // Test the client and server correctly handle empty settings. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-Empty-Client-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": {}}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| expectations: expectations, |
| flags: append([]string{ |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-application-settings", "proto,", |
| "-expect-peer-application-settings", "", |
| }, alpsFlags...), |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-Empty-Server-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": {}}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| expectations: expectations, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto,", |
| "-expect-peer-application-settings", "", |
| }, alpsFlags...), |
| }) |
| |
| bugs := ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsOld: true, |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsNew: true, |
| } |
| } |
| // Test the client rejects application settings from the server on |
| // protocols it doesn't have them. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto1"}, |
| ApplicationSettings: map[string][]byte{"proto1": []byte("runner")}, |
| Bugs: bugs, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| // The client supports ALPS with "proto2", but not "proto1". |
| flags: append([]string{ |
| "-advertise-alpn", "\x06proto1\x06proto2", |
| "-application-settings", "proto2,shim", |
| "-expect-alpn", "proto1", |
| }, alpsFlags...), |
| // The server sends ALPS with "proto1", which is invalid. |
| shouldFail: true, |
| expectedError: ":INVALID_ALPN_PROTOCOL:", |
| expectedLocalError: "remote error: illegal parameter", |
| }) |
| |
| // Test client rejects application settings from the server when |
| // server sends the wrong ALPS codepoint. |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsOld: true, |
| } |
| if alpsCodePoint == ALPSUseCodepointOld { |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsNew: true, |
| } |
| } |
| |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-WrongServerCodepoint-Client-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": {}}, |
| Bugs: bugs, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: append([]string{ |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-application-settings", "proto,", |
| "-expect-peer-application-settings", "", |
| }, alpsFlags...), |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| |
| // Test server ignore wrong codepoint from client. |
| clientSends := ALPSUseCodepointNew |
| if alpsCodePoint == ALPSUseCodepointNew { |
| clientSends = ALPSUseCodepointOld |
| } |
| |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-IgnoreClientWrongCodepoint-Server-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner1")}, |
| ALPSUseNewCodepoint: clientSends, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner2")}, |
| ALPSUseNewCodepoint: clientSends, |
| }, |
| resumeSession: true, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-on-initial-application-settings", "proto,shim1", |
| "-on-resume-application-settings", "proto,shim2", |
| }, alpsFlags...), |
| }) |
| |
| // Test the server declines ALPS if it doesn't support it for the |
| // specified protocol. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-UnsupportedProtocol-Server-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto1"}, |
| ApplicationSettings: map[string][]byte{"proto1": []byte("runner")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| // The server supports ALPS with "proto2", but not "proto1". |
| flags: append([]string{ |
| "-select-alpn", "proto1", |
| "-application-settings", "proto2,shim", |
| }, alpsFlags...), |
| }) |
| |
| // Test the client rejects application settings from the server when |
| // it always negotiate both codepoint. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-UnsupportedProtocol-Client-ServerBoth-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto1"}, |
| ApplicationSettings: map[string][]byte{"proto1": []byte("runner")}, |
| Bugs: ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsBoth: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: append([]string{ |
| "-advertise-alpn", "\x06proto1\x06proto2", |
| "-application-settings", "proto1,shim", |
| "-expect-alpn", "proto1", |
| }, alpsFlags...), |
| // The server sends ALPS with both application settings, which is invalid. |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: []byte("shim"), |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte("shim"), |
| } |
| } |
| |
| // Test that the server rejects a missing application_settings extension. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-OmitClientApplicationSettings-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner")}, |
| Bugs: ProtocolBugs{ |
| OmitClientApplicationSettings: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto,shim", |
| }, alpsFlags...), |
| // The runner is a client, so it only processes the shim's alert |
| // after checking connection state. |
| expectations: expectations, |
| shouldFail: true, |
| expectedError: ":MISSING_EXTENSION:", |
| expectedLocalError: "remote error: missing extension", |
| }) |
| |
| // Test that the server rejects a missing EncryptedExtensions message. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-OmitClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner")}, |
| Bugs: ProtocolBugs{ |
| OmitClientEncryptedExtensions: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto,shim", |
| }, alpsFlags...), |
| // The runner is a client, so it only processes the shim's alert |
| // after checking connection state. |
| expectations: expectations, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_MESSAGE:", |
| expectedLocalError: "remote error: unexpected message", |
| }) |
| |
| // Test that the server rejects an unexpected EncryptedExtensions message. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("UnexpectedClientEncryptedExtensions-%s-%s", alpsCodePoint, suffix), |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| AlwaysSendClientEncryptedExtensions: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_MESSAGE:", |
| expectedLocalError: "remote error: unexpected message", |
| }) |
| |
| // Test that the server rejects an unexpected extension in an |
| // expected EncryptedExtensions message. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ExtraClientEncryptedExtension-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner")}, |
| Bugs: ProtocolBugs{ |
| SendExtraClientEncryptedExtension: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto,shim", |
| }, alpsFlags...), |
| // The runner is a client, so it only processes the shim's alert |
| // after checking connection state. |
| expectations: expectations, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| |
| // Test that ALPS is carried over on 0-RTT. |
| // TODO(crbug.com/381113363): Support 0-RTT in DTLS 1.3. |
| if protocol != dtls { |
| for _, empty := range []bool{false, true} { |
| maybeEmpty := "" |
| runnerSettings := "runner" |
| shimSettings := "shim" |
| if empty { |
| maybeEmpty = "Empty-" |
| runnerSettings = "" |
| shimSettings = "" |
| } |
| |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: []byte(shimSettings), |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: []byte(shimSettings), |
| } |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-application-settings", "proto," + shimSettings, |
| "-expect-peer-application-settings", runnerSettings, |
| }, alpsFlags...), |
| expectations: expectations, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto," + shimSettings, |
| "-expect-peer-application-settings", runnerSettings, |
| }, alpsFlags...), |
| expectations: expectations, |
| }) |
| |
| // Sending application settings in 0-RTT handshakes is forbidden. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)}, |
| Bugs: ProtocolBugs{ |
| SendApplicationSettingsWithEarlyData: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-application-settings", "proto," + shimSettings, |
| "-expect-peer-application-settings", runnerSettings, |
| }, alpsFlags...), |
| expectations: expectations, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:", |
| expectedLocalError: "remote error: illegal parameter", |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)}, |
| Bugs: ProtocolBugs{ |
| SendApplicationSettingsWithEarlyData: true, |
| }, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto," + shimSettings, |
| "-expect-peer-application-settings", runnerSettings, |
| }, alpsFlags...), |
| expectations: expectations, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_MESSAGE:", |
| expectedLocalError: "remote error: unexpected message", |
| }) |
| } |
| |
| // Test that the client and server each decline early data if local |
| // ALPS preferences has changed for the current connection. |
| alpsMismatchTests := []struct { |
| name string |
| initialSettings, resumeSettings []byte |
| }{ |
| {"DifferentValues", []byte("settings1"), []byte("settings2")}, |
| {"OnOff", []byte("settings"), nil}, |
| {"OffOn", nil, []byte("settings")}, |
| // The empty settings value should not be mistaken for ALPS not |
| // being negotiated. |
| {"OnEmpty", []byte("settings"), []byte{}}, |
| {"EmptyOn", []byte{}, []byte("settings")}, |
| {"EmptyOff", []byte{}, nil}, |
| {"OffEmpty", nil, []byte{}}, |
| } |
| for _, test := range alpsMismatchTests { |
| flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"} |
| flags = append(flags, alpsFlags...) |
| if test.initialSettings != nil { |
| flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings)) |
| flags = append(flags, "-on-initial-expect-peer-application-settings", "runner") |
| } |
| if test.resumeSettings != nil { |
| flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings)) |
| flags = append(flags, "-on-resume-expect-peer-application-settings", "runner") |
| } |
| |
| expectations = connectionExpectations{ |
| peerApplicationSettingsOld: test.initialSettings, |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettingsOld: test.resumeSettings, |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| expectations = connectionExpectations{ |
| peerApplicationSettings: test.initialSettings, |
| } |
| resumeExpectations = &connectionExpectations{ |
| peerApplicationSettings: test.resumeSettings, |
| } |
| } |
| // The client should not offer early data if the session is |
| // inconsistent with the new configuration. Note that if |
| // the session did not negotiate ALPS (test.initialSettings |
| // is nil), the client always offers early data. |
| if test.initialSettings != nil { |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s-%s", test.name, alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| MaxEarlyDataSize: 16384, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| flags: append([]string{ |
| "-enable-early-data", |
| "-expect-ticket-supports-early-data", |
| "-expect-no-offer-early-data", |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| }, flags...), |
| expectations: expectations, |
| resumeExpectations: resumeExpectations, |
| }) |
| } |
| |
| // The server should reject early data if the session is |
| // inconsistent with the new selection. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s-%s", test.name, alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| expectEarlyDataRejected: true, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| }, flags...), |
| expectations: expectations, |
| resumeExpectations: resumeExpectations, |
| }) |
| } |
| |
| // Test that 0-RTT continues working when the shim configures |
| // ALPS but the peer does not. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Client-ServerDecline-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-application-settings", "proto,shim", |
| }, alpsFlags...), |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-EarlyData-Server-ClientNoOffe-%s-%s", alpsCodePoint, suffix), |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"proto"}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| earlyData: true, |
| flags: append([]string{ |
| "-select-alpn", "proto", |
| "-application-settings", "proto,shim", |
| }, alpsFlags...), |
| }) |
| } |
| } |
| } else { |
| // Test the client rejects the ALPS extension if the server |
| // negotiated TLS 1.2 or below. |
| for _, alpsCodePoint := range []ALPSUseCodepoint{ALPSUseCodepointNew, ALPSUseCodepointOld} { |
| useAlpsCodepointFlag := "0" |
| if alpsCodePoint == ALPSUseCodepointNew { |
| useAlpsCodepointFlag = "1" |
| } |
| |
| flags := []string{ |
| "-advertise-alpn", "\x03foo", |
| "-expect-alpn", "foo", |
| "-application-settings", "foo,shim", |
| "-alps-use-new-codepoint", useAlpsCodepointFlag, |
| } |
| bugs := ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsOld: true, |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsNew: true, |
| } |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-Reject-Client-%s-%s", alpsCodePoint, suffix), |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo"}, |
| ApplicationSettings: map[string][]byte{"foo": []byte("runner")}, |
| Bugs: bugs, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| |
| flags = []string{ |
| "-on-resume-advertise-alpn", "\x03foo", |
| "-on-resume-expect-alpn", "foo", |
| "-on-resume-application-settings", "foo,shim", |
| "-alps-use-new-codepoint", useAlpsCodepointFlag, |
| } |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsOld: true, |
| } |
| if alpsCodePoint == ALPSUseCodepointNew { |
| bugs = ProtocolBugs{ |
| AlwaysNegotiateApplicationSettingsNew: true, |
| } |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("ALPS-Reject-Client-Resume-%s-%s", alpsCodePoint, suffix), |
| config: Config{ |
| MaxVersion: ver.version, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo"}, |
| ApplicationSettings: map[string][]byte{"foo": []byte("runner")}, |
| Bugs: bugs, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| resumeSession: true, |
| flags: flags, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| |
| // Test the server declines ALPS if it negotiates TLS 1.2 or below. |
| flags = []string{ |
| "-select-alpn", "foo", |
| "-application-settings", "foo,shim", |
| "-alps-use-new-codepoint", useAlpsCodepointFlag, |
| } |
| |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("ALPS-Decline-Server-%s-%s", alpsCodePoint, suffix), |
| config: Config{ |
| MaxVersion: ver.version, |
| NextProtos: []string{"foo"}, |
| ApplicationSettings: map[string][]byte{"foo": []byte("runner")}, |
| ALPSUseNewCodepoint: alpsCodePoint, |
| }, |
| // Test both TLS 1.2 full and resumption handshakes. |
| resumeSession: true, |
| flags: flags, |
| // If not specified, runner and shim both implicitly expect ALPS |
| // is not negotiated. |
| }) |
| } |
| } |
| |
| // Test QUIC transport params |
| if protocol == quic { |
| // Client sends params |
| for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} { |
| for _, serverSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} { |
| useCodepointFlag := "0" |
| if clientConfig == QUICUseCodepointLegacy { |
| useCodepointFlag = "1" |
| } |
| flags := []string{ |
| "-quic-transport-params", |
| base64FlagValue([]byte{1, 2}), |
| "-quic-use-legacy-codepoint", useCodepointFlag, |
| } |
| expectations := connectionExpectations{ |
| quicTransportParams: []byte{1, 2}, |
| } |
| shouldFail := false |
| expectedError := "" |
| expectedLocalError := "" |
| if clientConfig == QUICUseCodepointLegacy { |
| expectations = connectionExpectations{ |
| quicTransportParamsLegacy: []byte{1, 2}, |
| } |
| } |
| if serverSends != clientConfig { |
| expectations = connectionExpectations{} |
| shouldFail = true |
| if serverSends == QUICUseCodepointNeither { |
| expectedError = ":MISSING_EXTENSION:" |
| } else { |
| expectedLocalError = "remote error: unsupported extension" |
| } |
| } else { |
| flags = append(flags, |
| "-expect-quic-transport-params", |
| base64FlagValue([]byte{3, 4})) |
| } |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| protocol: protocol, |
| name: fmt.Sprintf("QUICTransportParams-Client-Client%s-Server%s-%s", clientConfig, serverSends, suffix), |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| QUICTransportParams: []byte{3, 4}, |
| QUICTransportParamsUseLegacyCodepoint: serverSends, |
| }, |
| flags: flags, |
| expectations: expectations, |
| shouldFail: shouldFail, |
| expectedError: expectedError, |
| expectedLocalError: expectedLocalError, |
| skipTransportParamsConfig: true, |
| }) |
| } |
| } |
| // Server sends params |
| for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} { |
| for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} { |
| expectations := connectionExpectations{ |
| quicTransportParams: []byte{3, 4}, |
| } |
| shouldFail := false |
| expectedError := "" |
| useCodepointFlag := "0" |
| if serverConfig == QUICUseCodepointLegacy { |
| useCodepointFlag = "1" |
| expectations = connectionExpectations{ |
| quicTransportParamsLegacy: []byte{3, 4}, |
| } |
| } |
| flags := []string{ |
| "-quic-transport-params", |
| base64FlagValue([]byte{3, 4}), |
| "-quic-use-legacy-codepoint", useCodepointFlag, |
| } |
| if clientSends != QUICUseCodepointBoth && clientSends != serverConfig { |
| expectations = connectionExpectations{} |
| shouldFail = true |
| expectedError = ":MISSING_EXTENSION:" |
| } else { |
| flags = append(flags, |
| "-expect-quic-transport-params", |
| base64FlagValue([]byte{1, 2}), |
| ) |
| } |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| protocol: protocol, |
| name: fmt.Sprintf("QUICTransportParams-Server-Client%s-Server%s-%s", clientSends, serverConfig, suffix), |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| QUICTransportParams: []byte{1, 2}, |
| QUICTransportParamsUseLegacyCodepoint: clientSends, |
| }, |
| flags: flags, |
| expectations: expectations, |
| shouldFail: shouldFail, |
| expectedError: expectedError, |
| skipTransportParamsConfig: true, |
| }) |
| } |
| } |
| } else { |
| // Ensure non-QUIC client doesn't send QUIC transport parameters. |
| for _, clientConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} { |
| useCodepointFlag := "0" |
| if clientConfig == QUICUseCodepointLegacy { |
| useCodepointFlag = "1" |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: clientTest, |
| name: fmt.Sprintf("QUICTransportParams-Client-NotSentInNonQUIC-%s-%s", clientConfig, suffix), |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| QUICTransportParamsUseLegacyCodepoint: clientConfig, |
| }, |
| flags: []string{ |
| "-max-version", |
| ver.shimFlag(protocol), |
| "-quic-transport-params", |
| base64FlagValue([]byte{3, 4}), |
| "-quic-use-legacy-codepoint", useCodepointFlag, |
| }, |
| shouldFail: true, |
| expectedError: ":QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED:", |
| skipTransportParamsConfig: true, |
| }) |
| } |
| // Ensure non-QUIC server rejects codepoint 57 but ignores legacy 0xffa5. |
| for _, clientSends := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy, QUICUseCodepointBoth, QUICUseCodepointNeither} { |
| for _, serverConfig := range []QUICUseCodepoint{QUICUseCodepointStandard, QUICUseCodepointLegacy} { |
| shouldFail := false |
| expectedLocalError := "" |
| useCodepointFlag := "0" |
| if serverConfig == QUICUseCodepointLegacy { |
| useCodepointFlag = "1" |
| } |
| if clientSends == QUICUseCodepointStandard || clientSends == QUICUseCodepointBoth { |
| shouldFail = true |
| expectedLocalError = "remote error: unsupported extension" |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: fmt.Sprintf("QUICTransportParams-NonQUICServer-Client%s-Server%s-%s", clientSends, serverConfig, suffix), |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| QUICTransportParams: []byte{1, 2}, |
| QUICTransportParamsUseLegacyCodepoint: clientSends, |
| }, |
| flags: []string{ |
| "-quic-use-legacy-codepoint", useCodepointFlag, |
| }, |
| shouldFail: shouldFail, |
| expectedLocalError: expectedLocalError, |
| skipTransportParamsConfig: true, |
| }) |
| } |
| } |
| |
| } |
| |
| // Test ticket behavior. |
| |
| // Resume with a corrupt ticket. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "CorruptTicket-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| FilterTicket: func(in []byte) ([]byte, error) { |
| in[len(in)-1] ^= 1 |
| return in, nil |
| }, |
| }, |
| }, |
| resumeSession: true, |
| expectResumeRejected: true, |
| }) |
| // Test the ticket callbacks. |
| for _, aeadCallback := range []bool{false, true} { |
| flag := "-use-ticket-callback" |
| callbackSuffix := suffix |
| if aeadCallback { |
| flag = "-use-ticket-aead-callback" |
| callbackSuffix += "-AEAD" |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketCallback-" + callbackSuffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| }, |
| resumeSession: true, |
| flags: []string{flag}, |
| }) |
| // Only the old callback supports renewal. |
| if !aeadCallback { |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketCallback-Renew-" + callbackSuffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| ExpectNewTicket: true, |
| }, |
| }, |
| flags: []string{flag, "-renew-ticket"}, |
| resumeSession: true, |
| }) |
| } |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketCallback-Skip-" + callbackSuffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| ExpectNoNonEmptyNewSessionTicket: true, |
| }, |
| }, |
| flags: []string{flag, "-skip-ticket"}, |
| }) |
| |
| // Test that the ticket callback is only called once when everything before |
| // it in the ClientHello is asynchronous. This corrupts the ticket so |
| // certificate selection callbacks run. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketCallback-SingleCall-" + callbackSuffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| FilterTicket: func(in []byte) ([]byte, error) { |
| in[len(in)-1] ^= 1 |
| return in, nil |
| }, |
| }, |
| }, |
| resumeSession: true, |
| expectResumeRejected: true, |
| flags: []string{ |
| flag, |
| "-async", |
| }, |
| }) |
| } |
| |
| // Resume with various lengths of ticket session id. |
| if ver.version < VersionTLS13 { |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketSessionIDLength-0-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| EmptyTicketSessionID: true, |
| }, |
| }, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketSessionIDLength-16-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| TicketSessionIDLength: 16, |
| }, |
| }, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketSessionIDLength-32-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| TicketSessionIDLength: 32, |
| }, |
| }, |
| resumeSession: true, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "TicketSessionIDLength-33-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| TicketSessionIDLength: 33, |
| }, |
| }, |
| resumeSession: true, |
| shouldFail: true, |
| // The maximum session ID length is 32. |
| expectedError: ":CLIENTHELLO_PARSE_FAILED:", |
| }) |
| } |
| |
| // Basic DTLS-SRTP tests. Include fake profiles to ensure they |
| // are ignored. |
| if protocol == dtls { |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SRTP-Client-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "SRTP-Server-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, |
| }, |
| }) |
| // Test that the MKI is ignored. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "SRTP-Server-IgnoreMKI-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{SRTP_AES128_CM_HMAC_SHA1_80}, |
| Bugs: ProtocolBugs{ |
| SRTPMasterKeyIdentifier: "bogus", |
| }, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_80, |
| }, |
| }) |
| // Test that SRTP isn't negotiated on the server if there were |
| // no matching profiles. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "SRTP-Server-NoMatch-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{100, 101, 102}, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: 0, |
| }, |
| }) |
| // Test that the server returning an invalid SRTP profile is |
| // flagged as an error by the client. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SRTP-Client-NoMatch-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| SendSRTPProtectionProfile: SRTP_AES128_CM_HMAC_SHA1_32, |
| }, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80", |
| }, |
| shouldFail: true, |
| expectedError: ":BAD_SRTP_PROTECTION_PROFILE_LIST:", |
| }) |
| } else { |
| // DTLS-SRTP is not defined for other protocols. Configuring it |
| // on the client and server should ignore the extension. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SRTP-Client-Ignore-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: 0, |
| }, |
| }) |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "SRTP-Server-Ignore-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| SRTPProtectionProfiles: []uint16{40, SRTP_AES128_CM_HMAC_SHA1_80, 42}, |
| }, |
| flags: []string{ |
| "-srtp-profiles", |
| "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32", |
| }, |
| expectations: connectionExpectations{ |
| srtpProtectionProfile: 0, |
| }, |
| }) |
| } |
| |
| // Test SCT list. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SignedCertificateTimestampList-Client-" + suffix, |
| testType: clientTest, |
| config: Config{ |
| MaxVersion: ver.version, |
| Credential: rsaCertificate.WithSCTList(testSCTList), |
| }, |
| flags: []string{ |
| "-enable-signed-cert-timestamps", |
| "-expect-signed-cert-timestamps", |
| base64FlagValue(testSCTList), |
| }, |
| resumeSession: true, |
| }) |
| |
| var differentSCTList []byte |
| differentSCTList = append(differentSCTList, testSCTList...) |
| differentSCTList[len(differentSCTList)-1] ^= 1 |
| |
| // The SCT extension did not specify that it must only be sent on the inital handshake as it |
| // should have, so test that we tolerate but ignore it. This is only an issue pre-1.3, since |
| // SCTs are sent in the CertificateEntry message in 1.3, whereas they were previously sent |
| // in an extension in the ServerHello pre-1.3. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SendSCTListOnResume-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Credential: rsaCertificate.WithSCTList(testSCTList), |
| Bugs: ProtocolBugs{ |
| SendSCTListOnResume: differentSCTList, |
| }, |
| }, |
| flags: []string{ |
| "-enable-signed-cert-timestamps", |
| "-expect-signed-cert-timestamps", |
| base64FlagValue(testSCTList), |
| }, |
| resumeSession: true, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SignedCertificateTimestampList-Server-" + suffix, |
| testType: serverTest, |
| config: Config{ |
| MaxVersion: ver.version, |
| }, |
| shimCertificate: rsaCertificate.WithSCTList(testSCTList), |
| expectations: connectionExpectations{ |
| peerCertificate: rsaCertificate.WithSCTList(testSCTList), |
| }, |
| resumeSession: true, |
| }) |
| |
| // Test empty SCT list. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SignedCertificateTimestampListEmpty-Client-" + suffix, |
| testType: clientTest, |
| config: Config{ |
| MaxVersion: ver.version, |
| Credential: rsaCertificate.WithSCTList([]byte{0, 0}), |
| }, |
| flags: []string{ |
| "-enable-signed-cert-timestamps", |
| }, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| |
| // Test empty SCT in non-empty list. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| name: "SignedCertificateTimestampListEmptySCT-Client-" + suffix, |
| testType: clientTest, |
| config: Config{ |
| MaxVersion: ver.version, |
| Credential: rsaCertificate.WithSCTList([]byte{0, 6, 0, 2, 1, 2, 0, 0}), |
| }, |
| flags: []string{ |
| "-enable-signed-cert-timestamps", |
| }, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| |
| // Test that certificate-related extensions are not sent unsolicited. |
| testCases = append(testCases, testCase{ |
| protocol: protocol, |
| testType: serverTest, |
| name: "UnsolicitedCertificateExtensions-" + suffix, |
| config: Config{ |
| MaxVersion: ver.version, |
| Bugs: ProtocolBugs{ |
| NoOCSPStapling: true, |
| NoSignedCertificateTimestamps: true, |
| }, |
| }, |
| shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| }) |
| |
| // Extension permutation should interact correctly with other extensions, |
| // HelloVerifyRequest, HelloRetryRequest, and ECH. SSLTest.PermuteExtensions |
| // in ssl_test.cc tests that the extensions are actually permuted. This |
| // tests the handshake still works. |
| // |
| // This test also tests that all our extensions interact with each other. |
| for _, ech := range []bool{false, true} { |
| if ech && ver.version < VersionTLS13 { |
| continue |
| } |
| |
| test := testCase{ |
| protocol: protocol, |
| name: "AllExtensions-Client-Permute", |
| skipQUICALPNConfig: true, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| NextProtos: []string{"proto"}, |
| ApplicationSettings: map[string][]byte{"proto": []byte("runner1")}, |
| Bugs: ProtocolBugs{ |
| SendServerNameAck: true, |
| ExpectServerName: "example.com", |
| ExpectGREASE: true, |
| }, |
| }, |
| resumeSession: true, |
| flags: []string{ |
| "-permute-extensions", |
| "-enable-grease", |
| "-enable-ocsp-stapling", |
| "-enable-signed-cert-timestamps", |
| "-advertise-alpn", "\x05proto", |
| "-expect-alpn", "proto", |
| "-host-name", "example.com", |
| }, |
| } |
| |
| if ech { |
| test.name += "-ECH" |
| echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42}) |
| test.config.ServerECHConfigs = []ServerECHConfig{echConfig} |
| test.flags = append(test.flags, |
| "-ech-config-list", base64FlagValue(CreateECHConfigList(echConfig.ECHConfig.Raw)), |
| "-expect-ech-accept", |
| ) |
| test.expectations.echAccepted = true |
| } |
| |
| if ver.version >= VersionTLS13 { |
| // Trigger a HelloRetryRequest to test both ClientHellos. Note |
| // our DTLS tests always enable HelloVerifyRequest. |
| test.name += "-HelloRetryRequest" |
| |
| // ALPS is only available on TLS 1.3. |
| test.config.ApplicationSettings = map[string][]byte{"proto": []byte("runner")} |
| test.flags = append(test.flags, |
| "-application-settings", "proto,shim", |
| "-alps-use-new-codepoint", "1", |
| "-expect-peer-application-settings", "runner") |
| test.expectations.peerApplicationSettings = []byte("shim") |
| } |
| |
| if protocol == dtls { |
| test.config.SRTPProtectionProfiles = []uint16{SRTP_AES128_CM_HMAC_SHA1_80} |
| test.flags = append(test.flags, "-srtp-profiles", "SRTP_AES128_CM_SHA1_80") |
| test.expectations.srtpProtectionProfile = SRTP_AES128_CM_HMAC_SHA1_80 |
| } |
| |
| test.name += "-" + suffix |
| testCases = append(testCases, test) |
| } |
| } |
| } |
| |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "ClientHelloPadding", |
| config: Config{ |
| Bugs: ProtocolBugs{ |
| RequireClientHelloSize: 512, |
| }, |
| }, |
| // This hostname just needs to be long enough to push the |
| // ClientHello into F5's danger zone between 256 and 511 bytes |
| // long. |
| flags: []string{"-host-name", "01234567890123456789012345678901234567890123456789012345678901234567890123456789.com"}, |
| }) |
| |
| // Test that illegal extensions in TLS 1.3 are rejected by the client if |
| // in ServerHello. |
| testCases = append(testCases, testCase{ |
| name: "NPN-Forbidden-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| NextProtos: []string{"foo"}, |
| Bugs: ProtocolBugs{ |
| NegotiateNPNAtAllVersions: true, |
| }, |
| }, |
| flags: []string{"-select-next-proto", "foo"}, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| testCases = append(testCases, testCase{ |
| name: "EMS-Forbidden-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| NegotiateEMSAtAllVersions: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| testCases = append(testCases, testCase{ |
| name: "RenegotiationInfo-Forbidden-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| NegotiateRenegotiationInfoAtAllVersions: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| testCases = append(testCases, testCase{ |
| name: "Ticket-Forbidden-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| }, |
| resumeConfig: &Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| AdvertiseTicketExtension: true, |
| }, |
| }, |
| resumeSession: true, |
| shouldFail: true, |
| expectedError: ":ERROR_PARSING_EXTENSION:", |
| }) |
| |
| // Test that illegal extensions in TLS 1.3 are declined by the server if |
| // offered in ClientHello. The runner's server will fail if this occurs, |
| // so we exercise the offering path. (EMS and Renegotiation Info are |
| // implicit in every test.) |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "NPN-Declined-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| NextProtos: []string{"bar"}, |
| }, |
| flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"}, |
| }) |
| |
| // OpenSSL sends the status_request extension on resumption in TLS 1.2. Test that this is |
| // tolerated. |
| testCases = append(testCases, testCase{ |
| name: "SendOCSPResponseOnResume-TLS12", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Credential: rsaCertificate.WithOCSP(testOCSPResponse), |
| Bugs: ProtocolBugs{ |
| SendOCSPResponseOnResume: []byte("bogus"), |
| }, |
| }, |
| flags: []string{ |
| "-enable-ocsp-stapling", |
| "-expect-ocsp-response", |
| base64FlagValue(testOCSPResponse), |
| }, |
| resumeSession: true, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| name: "SendUnsolicitedOCSPOnCertificate-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| SendExtensionOnCertificate: testOCSPExtension, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| }) |
| |
| testCases = append(testCases, testCase{ |
| name: "SendUnsolicitedSCTOnCertificate-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| SendExtensionOnCertificate: testSCTExtension, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| }) |
| |
| // Test that extensions on client certificates are never accepted. |
| testCases = append(testCases, testCase{ |
| name: "SendExtensionOnClientCertificate-TLS13", |
| testType: serverTest, |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Credential: &rsaCertificate, |
| Bugs: ProtocolBugs{ |
| SendExtensionOnCertificate: testOCSPExtension, |
| }, |
| }, |
| flags: []string{ |
| "-enable-ocsp-stapling", |
| "-require-any-client-certificate", |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| }) |
| |
| testCases = append(testCases, testCase{ |
| name: "SendUnknownExtensionOnCertificate-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| SendExtensionOnCertificate: []byte{0x00, 0x7f, 0, 0}, |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| }) |
| |
| // Test that extensions on intermediates are allowed but ignored. |
| testCases = append(testCases, testCase{ |
| name: "IgnoreExtensionsOnIntermediates-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Credential: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| Bugs: ProtocolBugs{ |
| // Send different values on the intermediate. This tests |
| // the intermediate's extensions do not override the |
| // leaf's. |
| SendOCSPOnIntermediates: testOCSPResponse2, |
| SendSCTOnIntermediates: testSCTList2, |
| }, |
| }, |
| flags: []string{ |
| "-enable-ocsp-stapling", |
| "-expect-ocsp-response", |
| base64FlagValue(testOCSPResponse), |
| "-enable-signed-cert-timestamps", |
| "-expect-signed-cert-timestamps", |
| base64FlagValue(testSCTList), |
| }, |
| resumeSession: true, |
| }) |
| |
| // Test that extensions are not sent on intermediates when configured |
| // only for a leaf. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "SendNoExtensionsOnIntermediate-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| ExpectNoExtensionsOnIntermediate: true, |
| }, |
| }, |
| shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| }) |
| |
| // Test that extensions are not sent on client certificates. |
| testCases = append(testCases, testCase{ |
| name: "SendNoClientCertificateExtensions-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| ClientAuth: RequireAnyClientCert, |
| }, |
| shimCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| }) |
| |
| testCases = append(testCases, testCase{ |
| name: "SendDuplicateExtensionsOnCerts-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList), |
| Bugs: ProtocolBugs{ |
| SendDuplicateCertExtensions: true, |
| }, |
| }, |
| flags: []string{ |
| "-enable-ocsp-stapling", |
| "-enable-signed-cert-timestamps", |
| }, |
| resumeSession: true, |
| shouldFail: true, |
| expectedError: ":DUPLICATE_EXTENSION:", |
| }) |
| |
| testCases = append(testCases, testCase{ |
| name: "SignedCertificateTimestampListInvalid-Server", |
| testType: serverTest, |
| shimCertificate: rsaCertificate.WithSCTList([]byte{0, 0}), |
| shouldFail: true, |
| expectedError: ":INVALID_SCT_LIST:", |
| }) |
| } |
| |
| func addUnknownExtensionTests() { |
| // Test an unknown extension from the server. |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnknownExtension-Client", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| CustomExtension: "custom extension", |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnknownExtension-Client-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| CustomExtension: "custom extension", |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnknownUnencryptedExtension-Client-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| CustomUnencryptedExtension: "custom extension", |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| // The shim must send an alert, but alerts at this point do not |
| // get successfully decrypted by the runner. |
| expectedLocalError: "local error: bad record MAC", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnexpectedUnencryptedExtension-Client-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| SendUnencryptedALPN: "foo", |
| }, |
| }, |
| flags: []string{ |
| "-advertise-alpn", "\x03foo\x03bar", |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| // The shim must send an alert, but alerts at this point do not |
| // get successfully decrypted by the runner. |
| expectedLocalError: "local error: bad record MAC", |
| }) |
| |
| // Test a known but unoffered extension from the server. |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnofferedExtension-Client", |
| config: Config{ |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| SendALPN: "alpn", |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "UnofferedExtension-Client-TLS13", |
| config: Config{ |
| MaxVersion: VersionTLS13, |
| Bugs: ProtocolBugs{ |
| SendALPN: "alpn", |
| }, |
| }, |
| shouldFail: true, |
| expectedError: ":UNEXPECTED_EXTENSION:", |
| expectedLocalError: "remote error: unsupported extension", |
| }) |
| } |
| |
| // Test that omitted and empty extensions blocks are tolerated. |
| func addOmitExtensionsTests() { |
| // Check the ExpectOmitExtensions setting works. |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "ExpectOmitExtensions", |
| config: Config{ |
| MinVersion: VersionTLS12, |
| MaxVersion: VersionTLS12, |
| Bugs: ProtocolBugs{ |
| ExpectOmitExtensions: true, |
| }, |
| }, |
| shouldFail: true, |
| expectedLocalError: "tls: ServerHello did not omit extensions", |
| }) |
| |
| for _, ver := range tlsVersions { |
| if ver.version > VersionTLS12 { |
| continue |
| } |
| |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "OmitExtensions-ClientHello-" + ver.name, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| SessionTicketsDisabled: true, |
| Bugs: ProtocolBugs{ |
| OmitExtensions: true, |
| // With no client extensions, the ServerHello must not have |
| // extensions. It should then omit the extensions field. |
| ExpectOmitExtensions: true, |
| }, |
| }, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| testType: serverTest, |
| name: "EmptyExtensions-ClientHello-" + ver.name, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| SessionTicketsDisabled: true, |
| Bugs: ProtocolBugs{ |
| EmptyExtensions: true, |
| // With no client extensions, the ServerHello must not have |
| // extensions. It should then omit the extensions field. |
| ExpectOmitExtensions: true, |
| }, |
| }, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "OmitExtensions-ServerHello-" + ver.name, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| SessionTicketsDisabled: true, |
| Bugs: ProtocolBugs{ |
| OmitExtensions: true, |
| // Disable all ServerHello extensions so |
| // OmitExtensions works. |
| NoExtendedMasterSecret: true, |
| NoRenegotiationInfo: true, |
| NoOCSPStapling: true, |
| NoSignedCertificateTimestamps: true, |
| }, |
| }, |
| }) |
| |
| testCases = append(testCases, testCase{ |
| testType: clientTest, |
| name: "EmptyExtensions-ServerHello-" + ver.name, |
| config: Config{ |
| MinVersion: ver.version, |
| MaxVersion: ver.version, |
| SessionTicketsDisabled: true, |
| Bugs: ProtocolBugs{ |
| EmptyExtensions: true, |
| // Disable all ServerHello extensions so |
| // EmptyExtensions works. |
| NoExtendedMasterSecret: true, |
| NoRenegotiationInfo: true, |
| NoOCSPStapling: true, |
| NoSignedCertificateTimestamps: true, |
| }, |
| }, |
| }) |
| } |
| } |