Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
2342 lines
76 KiB
Go
2342 lines
76 KiB
Go
// 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 (
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"fmt"
|
|
"math/big"
|
|
"time"
|
|
)
|
|
|
|
func addExtensionTests() {
|
|
exampleCertificate := generateSingleCertChain(&x509.Certificate{
|
|
SerialNumber: big.NewInt(57005),
|
|
Subject: pkix.Name{
|
|
CommonName: "test cert",
|
|
},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(time.Hour),
|
|
DNSNames: []string{"example.com"},
|
|
IsCA: true,
|
|
BasicConstraintsValid: true,
|
|
}, &ecdsaP256Key)
|
|
|
|
// 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,
|
|
})
|
|
|
|
// 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} {
|
|
flags := []string{}
|
|
expectations := connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim1"),
|
|
}
|
|
resumeExpectations := &connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim2"),
|
|
}
|
|
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
flags = append(flags, "-alps-use-new-codepoint")
|
|
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{}
|
|
expectations = connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim1"),
|
|
}
|
|
resumeExpectations = &connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim2"),
|
|
}
|
|
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
flags = append(flags, "-alps-use-new-codepoint")
|
|
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{}
|
|
expectations = connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim1"),
|
|
}
|
|
resumeExpectations = &connectionExpectations{
|
|
peerApplicationSettingsOld: []byte("shim2"),
|
|
}
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
alpsFlags = append(alpsFlags, "-alps-use-new-codepoint")
|
|
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} {
|
|
flags := []string{
|
|
"-advertise-alpn", "\x03foo",
|
|
"-expect-alpn", "foo",
|
|
"-application-settings", "foo,shim",
|
|
}
|
|
bugs := ProtocolBugs{
|
|
AlwaysNegotiateApplicationSettingsOld: true,
|
|
}
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
flags = append(flags, "-alps-use-new-codepoint")
|
|
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",
|
|
}
|
|
bugs = ProtocolBugs{
|
|
AlwaysNegotiateApplicationSettingsOld: true,
|
|
}
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
flags = append(flags, "-alps-use-new-codepoint")
|
|
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",
|
|
}
|
|
if alpsCodePoint == ALPSUseCodepointNew {
|
|
flags = append(flags, "-alps-use-new-codepoint")
|
|
}
|
|
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",
|
|
"-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,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
}
|