Split runner.go into a bunch of different files

Now runner.go contains only the test runner, while the various test
suites are moved into their own files, named foo_tests.go. (foo_test.go
would be treated as a Go test.)

I broadly just split by the addFooTests functions, but in a few cases I
grouped them together.

Now we no longer have a single 24,000 line file with all the tests. That
was getting unwieldy.

Change-Id: I76f372f60f5f0de5f1ba0913317918a4053372a3
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/77107
Reviewed-by: Bob Beck <bbe@google.com>
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
This commit is contained in:
David Benjamin
2025-03-06 14:27:40 -08:00
committed by Boringssl LUCI CQ
parent e1d51d6466
commit cbc61792ca
33 changed files with 22384 additions and 21814 deletions
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addCBCPaddingTests() {
testCases = append(testCases, testCase{
name: "MaxCBCPadding",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
MaxPadding: true,
},
},
messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
})
testCases = append(testCases, testCase{
name: "BadCBCPadding",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
PaddingFirstByteBad: true,
},
},
shouldFail: true,
expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
})
// OpenSSL previously had an issue where the first byte of padding in
// 255 bytes of padding wasn't checked.
testCases = append(testCases, testCase{
name: "BadCBCPadding255",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
MaxPadding: true,
PaddingFirstByteBadIf255: true,
},
},
messageLen: 12, // 20 bytes of SHA-1 + 12 == 0 % block size
shouldFail: true,
expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
})
}
func addCBCSplittingTests() {
cbcCiphers := []struct {
name string
cipher uint16
}{
{"3DES", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
{"AES128", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
{"AES256", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
}
for _, t := range cbcCiphers {
testCases = append(testCases, testCase{
name: "CBCRecordSplitting-" + t.name,
config: Config{
MaxVersion: VersionTLS10,
MinVersion: VersionTLS10,
CipherSuites: []uint16{t.cipher},
Bugs: ProtocolBugs{
ExpectRecordSplitting: true,
},
},
messageLen: -1, // read until EOF
resumeSession: true,
flags: []string{
"-async",
"-write-different-record-sizes",
"-cbc-record-splitting",
// BoringSSL disables 3DES by default.
"-cipher", "ALL:3DES",
},
})
testCases = append(testCases, testCase{
name: "CBCRecordSplittingPartialWrite-" + t.name,
config: Config{
MaxVersion: VersionTLS10,
MinVersion: VersionTLS10,
CipherSuites: []uint16{t.cipher},
Bugs: ProtocolBugs{
ExpectRecordSplitting: true,
},
},
messageLen: -1, // read until EOF
flags: []string{
"-async",
"-write-different-record-sizes",
"-cbc-record-splitting",
"-partial-write",
// BoringSSL disables 3DES by default.
"-cipher", "ALL:3DES",
},
})
}
}
+323
View File
@@ -0,0 +1,323 @@
// 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 (
"bytes"
"crypto/rand"
"fmt"
"strconv"
)
const (
shrinkingCompressionAlgID = 0xff01
expandingCompressionAlgID = 0xff02
randomCompressionAlgID = 0xff03
)
var (
// shrinkingPrefix is the first two bytes of a Certificate message.
shrinkingPrefix = []byte{0, 0}
// expandingPrefix is just some arbitrary byte string. This has to match the
// value in the shim.
expandingPrefix = []byte{1, 2, 3, 4}
)
var shrinkingCompression = CertCompressionAlg{
Compress: func(uncompressed []byte) []byte {
if !bytes.HasPrefix(uncompressed, shrinkingPrefix) {
panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed))
}
return uncompressed[len(shrinkingPrefix):]
},
Decompress: func(out []byte, compressed []byte) bool {
if len(out) != len(shrinkingPrefix)+len(compressed) {
return false
}
copy(out, shrinkingPrefix)
copy(out[len(shrinkingPrefix):], compressed)
return true
},
}
var expandingCompression = CertCompressionAlg{
Compress: func(uncompressed []byte) []byte {
ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed))
ret = append(ret, expandingPrefix...)
return append(ret, uncompressed...)
},
Decompress: func(out []byte, compressed []byte) bool {
if !bytes.HasPrefix(compressed, expandingPrefix) {
return false
}
copy(out, compressed[len(expandingPrefix):])
return true
},
}
var randomCompression = CertCompressionAlg{
Compress: func(uncompressed []byte) []byte {
ret := make([]byte, 1+len(uncompressed))
if _, err := rand.Read(ret[:1]); err != nil {
panic(err)
}
copy(ret[1:], uncompressed)
return ret
},
Decompress: func(out []byte, compressed []byte) bool {
if len(compressed) != 1+len(out) {
return false
}
copy(out, compressed[1:])
return true
},
}
func addCertCompressionTests() {
for _, ver := range tlsVersions {
if ver.version < VersionTLS12 {
continue
}
// Duplicate compression algorithms is an error, even if nothing is
// configured.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DuplicateCertCompressionExt-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
DuplicateCompressedCertAlgs: true,
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
// With compression algorithms configured, an duplicate values should still
// be an error.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DuplicateCertCompressionExt2-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
DuplicateCompressedCertAlgs: true,
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
if ver.version < VersionTLS13 {
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionIgnoredBefore13-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
expandingCompressionAlgID: expandingCompression,
},
},
})
continue
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionExpands-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
expandingCompressionAlgID: expandingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: expandingCompressionAlgID,
},
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionShrinks-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
},
},
})
// Test that the shim behaves consistently if the compression function
// is non-deterministic. This is intended to model version differences
// between the shim and handshaker with handshake hints, but it is also
// useful in confirming we only call the callbacks once.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionRandom-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
randomCompressionAlgID: randomCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: randomCompressionAlgID,
},
},
})
// With both algorithms configured, the server should pick its most
// preferable. (Which is expandingCompressionAlgID.)
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionPriority-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
expandingCompressionAlgID: expandingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: expandingCompressionAlgID,
},
},
})
// With no common algorithms configured, the server should decline
// compression.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CertCompressionNoCommonAlgs-" + ver.name,
flags: []string{"-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID)},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
expandingCompressionAlgID: expandingCompression,
},
Bugs: ProtocolBugs{
ExpectUncompressedCert: true,
},
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "CertCompressionExpandsClient-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
expandingCompressionAlgID: expandingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: expandingCompressionAlgID,
},
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "CertCompressionShrinksClient-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
},
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "CertCompressionBadAlgIDClient-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
SendCertCompressionAlgID: 1234,
},
},
shouldFail: true,
expectedError: ":UNKNOWN_CERT_COMPRESSION_ALG:",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "CertCompressionTooSmallClient-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
SendCertUncompressedLength: 12,
},
},
shouldFail: true,
expectedError: ":CERT_DECOMPRESSION_FAILED:",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "CertCompressionTooLargeClient-" + ver.name,
flags: []string{"-install-cert-compression-algs"},
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
SendCertUncompressedLength: 1 << 20,
},
},
shouldFail: true,
expectedError: ":UNCOMPRESSED_CERT_TOO_LARGE:",
})
}
}
@@ -0,0 +1,688 @@
// 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"
"strconv"
)
func canBeShimCertificate(c *Credential) bool {
// Some options can only be set with the credentials API.
return c.Type == CredentialTypeX509 && !c.MustMatchIssuer && c.TrustAnchorID == nil
}
func addCertificateSelectionTests() {
// Combinatorially test each selection criteria at different versions,
// protocols, and with the matching certificate before and after the
// mismatching one.
type certSelectTest struct {
name string
testType testType
minVersion uint16
maxVersion uint16
config Config
match *Credential
mismatch *Credential
flags []string
expectedError string
}
certSelectTests := []certSelectTest{
// TLS 1.0 through TLS 1.2 servers should incorporate TLS cipher suites
// into certificate selection.
{
name: "Server-CipherSuite-ECDHE_ECDSA",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
},
},
match: &ecdsaP256Certificate,
mismatch: &rsaCertificate,
expectedError: ":NO_SHARED_CIPHER:",
},
{
name: "Server-CipherSuite-ECDHE_RSA",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
},
},
match: &rsaCertificate,
mismatch: &ecdsaP256Certificate,
expectedError: ":NO_SHARED_CIPHER:",
},
{
name: "Server-CipherSuite-RSA",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_RSA_WITH_AES_128_CBC_SHA,
},
},
match: &rsaCertificate,
mismatch: &ecdsaP256Certificate,
expectedError: ":NO_SHARED_CIPHER:",
},
// Ed25519 counts as ECDSA for purposes of cipher suite matching.
{
name: "Server-CipherSuite-ECDHE_ECDSA-Ed25519",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
},
},
match: &ed25519Certificate,
mismatch: &rsaCertificate,
expectedError: ":NO_SHARED_CIPHER:",
},
{
name: "Server-CipherSuite-ECDHE_RSA-Ed25519",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
},
},
match: &rsaCertificate,
mismatch: &ed25519Certificate,
expectedError: ":NO_SHARED_CIPHER:",
},
// If there is no ECDHE curve match, ECDHE cipher suites are
// disqualified in TLS 1.2 and below. This, in turn, impacts the
// available cipher suites for each credential.
{
name: "Server-CipherSuite-NoECDHE",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CurvePreferences: []CurveID{CurveP256},
},
flags: []string{"-curves", strconv.Itoa(int(CurveX25519))},
match: &rsaCertificate,
mismatch: &ecdsaP256Certificate,
expectedError: ":NO_SHARED_CIPHER:",
},
// If the client offered a cipher that would allow a certificate, but it
// wasn't one of the ones we configured, the certificate should be
// skipped in favor of another one.
{
name: "Server-CipherSuite-Prefs",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CipherSuites: []uint16{
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
},
},
flags: []string{"-cipher", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
match: &rsaCertificate,
mismatch: &ecdsaP256Certificate,
expectedError: ":NO_SHARED_CIPHER:",
},
// TLS 1.0 through 1.2 servers should incorporate the curve list into
// ECDSA certificate selection.
{
name: "Server-Curve",
testType: serverTest,
maxVersion: VersionTLS12,
config: Config{
CurvePreferences: []CurveID{CurveP256},
},
match: &ecdsaP256Certificate,
mismatch: &ecdsaP384Certificate,
expectedError: ":WRONG_CURVE:",
},
// TLS 1.3 servers ignore the curve list. ECDSA certificate selection is
// solely determined by the signature algorithm list.
{
name: "Server-IgnoreCurve",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
CurvePreferences: []CurveID{CurveP256},
},
match: &ecdsaP384Certificate,
},
// TLS 1.2 servers also ignore the curve list for Ed25519. The signature
// algorithm list is sufficient for Ed25519.
{
name: "Server-IgnoreCurveEd25519",
testType: serverTest,
minVersion: VersionTLS12,
config: Config{
CurvePreferences: []CurveID{CurveP256},
},
match: &ed25519Certificate,
},
// Without signature algorithm negotiation, Ed25519 is not usable in TLS
// 1.1 and below.
{
name: "Server-NoEd25519",
testType: serverTest,
maxVersion: VersionTLS11,
match: &rsaCertificate,
mismatch: &ed25519Certificate,
},
// TLS 1.2 and up should incorporate the signature algorithm list into
// certificate selection.
{
name: "Server-SignatureAlgorithm",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
CipherSuites: []uint16{
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
},
},
match: &ecdsaP256Certificate,
mismatch: &rsaCertificate,
expectedError: ":NO_SHARED_CIPHER:",
},
{
name: "Server-SignatureAlgorithm",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP256Certificate,
mismatch: &rsaCertificate,
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// TLS 1.2's use of the signature algorithm list only disables the
// signing-based algorithms. If an RSA key exchange cipher suite is
// eligible, that is fine. (This is not a realistic configuration,
// however. No one would configure RSA before ECDSA.)
{
name: "Server-SignatureAlgorithmImpactsECDHEOnly",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
CipherSuites: []uint16{
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_128_CBC_SHA,
},
},
match: &rsaCertificate,
},
// TLS 1.3's use of the signature algorithm looks at the ECDSA curve
// embedded in the signature algorithm.
{
name: "Server-SignatureAlgorithmECDSACurve",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP256Certificate,
mismatch: &ecdsaP384Certificate,
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// TLS 1.2's use does not.
{
name: "Server-SignatureAlgorithmECDSACurve",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP384Certificate,
},
// TLS 1.0 and 1.1 do not look at the signature algorithm.
{
name: "Server-IgnoreSignatureAlgorithm",
testType: serverTest,
maxVersion: VersionTLS11,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &rsaCertificate,
},
// Signature algorithm matches take preferences on the keys into
// consideration.
{
name: "Server-SignatureAlgorithmKeyPrefs",
testType: serverTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
expectedError: ":NO_SHARED_CIPHER:",
},
{
name: "Server-SignatureAlgorithmKeyPrefs",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
},
match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// TLS 1.2 clients and below check the certificate against the old
// client certificate types field.
{
name: "Client-ClientCertificateTypes-RSA",
testType: clientTest,
maxVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
ClientCertificateTypes: []uint8{CertTypeRSASign},
},
match: &rsaCertificate,
mismatch: &ecdsaP256Certificate,
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
},
{
name: "Client-ClientCertificateTypes-ECDSA",
testType: clientTest,
maxVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
ClientCertificateTypes: []uint8{CertTypeECDSASign},
},
match: &ecdsaP256Certificate,
mismatch: &rsaCertificate,
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
},
// Ed25519 is considered ECDSA for purposes of client certificate types.
{
name: "Client-ClientCertificateTypes-RSA-Ed25519",
testType: clientTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
ClientCertificateTypes: []uint8{CertTypeRSASign},
},
match: &rsaCertificate,
mismatch: &ed25519Certificate,
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
},
{
name: "Client-ClientCertificateTypes-ECDSA-Ed25519",
testType: clientTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
ClientCertificateTypes: []uint8{CertTypeECDSASign},
},
match: &ed25519Certificate,
mismatch: &rsaCertificate,
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
},
// TLS 1.2 and up should incorporate the signature algorithm list into
// certificate selection. (There is no signature algorithm list to look
// at in TLS 1.0 and 1.1.)
{
name: "Client-SignatureAlgorithm",
testType: clientTest,
minVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP256Certificate,
mismatch: &rsaCertificate,
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// TLS 1.3's use of the signature algorithm looks at the ECDSA curve
// embedded in the signature algorithm.
{
name: "Client-SignatureAlgorithmECDSACurve",
testType: clientTest,
minVersion: VersionTLS13,
config: Config{
ClientAuth: RequestClientCert,
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP256Certificate,
mismatch: &ecdsaP384Certificate,
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// TLS 1.2's use does not. It is not possible to determine what ECDSA
// curves are allowed by the server.
{
name: "Client-SignatureAlgorithmECDSACurve",
testType: clientTest,
minVersion: VersionTLS12,
maxVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
match: &ecdsaP384Certificate,
},
// Signature algorithm matches take preferences on the keys into
// consideration.
{
name: "Client-SignatureAlgorithmKeyPrefs",
testType: clientTest,
minVersion: VersionTLS12,
config: Config{
ClientAuth: RequestClientCert,
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
},
match: rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
mismatch: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
},
// By default, certificate selection does not take issuers
// into account.
{
name: "Client-DontCheckIssuer",
testType: clientTest,
config: Config{
ClientAuth: RequestClientCert,
ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
},
match: &ecdsaP256Certificate,
},
{
name: "Server-DontCheckIssuer",
testType: serverTest,
config: Config{
RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
SendRootCAs: true,
},
match: &ecdsaP256Certificate,
},
// If requested, certificate selection will match against the
// requested issuers.
{
name: "Client-CheckIssuer",
testType: clientTest,
config: Config{
ClientAuth: RequestClientCert,
ClientCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
},
match: rsaChainCertificate.WithMustMatchIssuer(true),
mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
expectedError: ":NO_MATCHING_ISSUER:",
},
{
name: "Server-CheckIssuer",
testType: serverTest,
config: Config{
RootCAs: makeCertPoolFromRoots(&rsaChainCertificate, &ecdsaP384Certificate),
SendRootCAs: true,
},
match: rsaChainCertificate.WithMustMatchIssuer(true),
mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
expectedError: ":NO_MATCHING_ISSUER:",
},
// Trust anchor IDs can also be used to match issuers.
// TODO(crbug.com/398275713): Implement this for client certificates.
{
name: "Server-CheckIssuer-TrustAnchorIDs",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
RequestTrustAnchors: [][]byte{{1, 1, 1}},
},
match: rsaChainCertificate.WithTrustAnchorID([]byte{1, 1, 1}),
mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
expectedError: ":NO_MATCHING_ISSUER:",
},
// When an issuer-gated credential fails, a normal credential may be
// selected instead.
{
name: "Client-CheckIssuerFallback",
testType: clientTest,
config: Config{
ClientAuth: RequestClientCert,
ClientCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
},
match: &rsaChainCertificate,
mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
expectedError: ":NO_MATCHING_ISSUER:",
},
{
name: "Server-CheckIssuerFallback",
testType: serverTest,
config: Config{
RootCAs: makeCertPoolFromRoots(&ecdsaP384Certificate),
SendRootCAs: true,
},
match: &rsaChainCertificate,
mismatch: ecdsaP256Certificate.WithMustMatchIssuer(true),
expectedError: ":NO_MATCHING_ISSUER:",
},
{
name: "Server-CheckIssuerFallback-TrustAnchorIDs",
testType: serverTest,
minVersion: VersionTLS13,
config: Config{
RequestTrustAnchors: [][]byte{{1, 1, 1}},
},
match: &rsaChainCertificate,
mismatch: ecdsaP256Certificate.WithTrustAnchorID([]byte{2, 2, 2}),
expectedError: ":NO_MATCHING_ISSUER:",
},
}
for _, protocol := range []protocol{tls, dtls} {
for _, vers := range allVersions(protocol) {
suffix := fmt.Sprintf("%s-%s", protocol, vers)
// Test that the credential list is interpreted in preference order,
// with the default credential, if any, at the end.
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-Client-PreferenceOrder-%s", suffix),
testType: clientTest,
protocol: protocol,
config: Config{
MinVersion: vers.version,
MaxVersion: vers.version,
ClientAuth: RequestClientCert,
},
shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
shimCertificate: &rsaCertificate,
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
})
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-Server-PreferenceOrder-%s", suffix),
testType: serverTest,
protocol: protocol,
config: Config{
MinVersion: vers.version,
MaxVersion: vers.version,
},
shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
shimCertificate: &rsaCertificate,
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
})
// Test that the selected credential contributes the certificate chain, OCSP response,
// and SCT list.
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-Server-OCSP-SCT-%s", suffix),
testType: serverTest,
protocol: protocol,
config: Config{
MinVersion: vers.version,
MaxVersion: vers.version,
// Configure enough options so that, at all TLS versions, only an RSA
// certificate will be accepted.
CipherSuites: []uint16{
TLS_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
},
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
},
shimCredentials: []*Credential{
ecdsaP256Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
},
shimCertificate: ecdsaP384Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
},
})
// Test that the credentials API works asynchronously. This tests both deferring the
// configuration to the certificate callback, and using a custom, async private key.
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-Client-Async-%s", suffix),
testType: clientTest,
protocol: protocol,
config: Config{
MinVersion: vers.version,
MaxVersion: vers.version,
ClientAuth: RequestClientCert,
},
shimCredentials: []*Credential{&ecdsaP256Certificate},
shimCertificate: &rsaCertificate,
flags: []string{"-async", "-expect-selected-credential", "0"},
expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
})
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-Server-Async-%s", suffix),
testType: serverTest,
protocol: protocol,
config: Config{
MinVersion: vers.version,
MaxVersion: vers.version,
},
shimCredentials: []*Credential{&ecdsaP256Certificate},
shimCertificate: &rsaCertificate,
flags: []string{"-async", "-expect-selected-credential", "0"},
expectations: connectionExpectations{peerCertificate: &ecdsaP256Certificate},
})
for _, test := range certSelectTests {
if test.minVersion != 0 && vers.version < test.minVersion {
continue
}
if test.maxVersion != 0 && vers.version > test.maxVersion {
continue
}
config := test.config
config.MinVersion = vers.version
config.MaxVersion = vers.version
// If the mismatch field is omitted, this is a positive test,
// just to confirm that the selection logic does not block a
// particular certificate.
if test.mismatch == nil {
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-%s-%s", test.name, suffix),
protocol: protocol,
testType: test.testType,
config: config,
shimCredentials: []*Credential{test.match},
flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
expectations: connectionExpectations{peerCertificate: test.match},
})
continue
}
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-%s-MatchFirst-%s", test.name, suffix),
protocol: protocol,
testType: test.testType,
config: config,
shimCredentials: []*Credential{test.match, test.mismatch},
flags: append([]string{"-expect-selected-credential", "0"}, test.flags...),
expectations: connectionExpectations{peerCertificate: test.match},
})
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-%s-MatchSecond-%s", test.name, suffix),
protocol: protocol,
testType: test.testType,
config: config,
shimCredentials: []*Credential{test.mismatch, test.match},
flags: append([]string{"-expect-selected-credential", "1"}, test.flags...),
expectations: connectionExpectations{peerCertificate: test.match},
})
if canBeShimCertificate(test.match) {
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-%s-MatchDefault-%s", test.name, suffix),
protocol: protocol,
testType: test.testType,
config: config,
shimCredentials: []*Credential{test.mismatch},
shimCertificate: test.match,
flags: append([]string{"-expect-selected-credential", "-1"}, test.flags...),
expectations: connectionExpectations{peerCertificate: test.match},
})
}
testCases = append(testCases, testCase{
name: fmt.Sprintf("CertificateSelection-%s-MatchNone-%s", test.name, suffix),
protocol: protocol,
testType: test.testType,
config: config,
shimCredentials: []*Credential{test.mismatch, test.mismatch, test.mismatch},
flags: test.flags,
shouldFail: true,
expectedLocalError: "remote error: handshake failure",
expectedError: test.expectedError,
})
}
}
}
}
+409
View File
@@ -0,0 +1,409 @@
// 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"
func makeCertPoolFromRoots(creds ...*Credential) *x509.CertPool {
certPool := x509.NewCertPool()
for _, cred := range creds {
cert, err := x509.ParseCertificate(cred.RootCertificate)
if err != nil {
panic(err)
}
certPool.AddCert(cert)
}
return certPool
}
func addClientAuthTests() {
// Add a dummy cert pool to stress certificate authority parsing.
certPool := makeCertPoolFromRoots(&rsaCertificate, &rsa1024Certificate)
caNames := certPool.Subjects()
for _, ver := range tlsVersions {
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-ClientAuth-RSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: RequireAnyClientCert,
ClientCAs: certPool,
},
shimCertificate: &rsaCertificate,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-Server-ClientAuth-RSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
},
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-Server-ClientAuth-ECDSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &ecdsaP256Certificate,
},
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-ClientAuth-ECDSA",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: RequireAnyClientCert,
ClientCAs: certPool,
},
shimCertificate: &ecdsaP256Certificate,
})
testCases = append(testCases, testCase{
name: "NoClientCertificate-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: RequireAnyClientCert,
},
shouldFail: true,
expectedLocalError: "client didn't provide a certificate",
})
testCases = append(testCases, testCase{
// Even if not configured to expect a certificate, OpenSSL will
// return X509_V_OK as the verify_result.
testType: serverTest,
name: "NoClientCertificateRequested-Server-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
flags: []string{
"-expect-verify-result",
},
resumeSession: true,
})
testCases = append(testCases, testCase{
// If a client certificate is not provided, OpenSSL will still
// return X509_V_OK as the verify_result.
testType: serverTest,
name: "NoClientCertificate-Server-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
flags: []string{
"-expect-verify-result",
"-verify-peer",
},
resumeSession: true,
})
certificateRequired := "remote error: certificate required"
if ver.version < VersionTLS13 {
// Prior to TLS 1.3, the generic handshake_failure alert
// was used.
certificateRequired = "remote error: handshake failure"
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "RequireAnyClientCertificate-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
flags: []string{"-require-any-client-certificate"},
shouldFail: true,
expectedError: ":PEER_DID_NOT_RETURN_A_CERTIFICATE:",
expectedLocalError: certificateRequired,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SkipClientCertificate-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SkipClientCertificate: true,
},
},
// Setting SSL_VERIFY_PEER allows anonymous clients.
flags: []string{"-verify-peer"},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-Server-CertReq-CA-List",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
Bugs: ProtocolBugs{
ExpectCertificateReqNames: caNames,
},
},
flags: []string{
"-require-any-client-certificate",
"-use-client-ca-list", encodeDERValues(caNames),
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: ver.name + "-Client-CertReq-CA-List",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
ClientAuth: RequireAnyClientCert,
ClientCAs: certPool,
},
shimCertificate: &rsaCertificate,
flags: []string{
"-expect-client-ca-list", encodeDERValues(caNames),
},
})
}
// Client auth is only legal in certificate-based ciphers.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ClientAuth-PSK",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
ClientAuth: RequireAnyClientCert,
},
shimCertificate: &rsaCertificate,
flags: []string{
"-psk", "secret",
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ClientAuth-ECDHE_PSK",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
ClientAuth: RequireAnyClientCert,
},
shimCertificate: &rsaCertificate,
flags: []string{
"-psk", "secret",
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
// Regression test for a bug where the client CA list, if explicitly
// set to NULL, was mis-encoded.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Null-Client-CA-List",
config: Config{
MaxVersion: VersionTLS12,
Credential: &rsaCertificate,
Bugs: ProtocolBugs{
ExpectCertificateReqNames: [][]byte{},
},
},
flags: []string{
"-require-any-client-certificate",
"-use-client-ca-list", "<NULL>",
},
})
// Test that an empty client CA list doesn't send a CA extension.
// (This is implicitly tested by the parser. An empty CA extension is
// a syntax error.)
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-Empty-Client-CA-List",
config: Config{
MaxVersion: VersionTLS13,
Credential: &rsaCertificate,
},
flags: []string{
"-require-any-client-certificate",
"-use-client-ca-list", "<EMPTY>",
},
})
}
func addCertificateTests() {
for _, ver := range tlsVersions {
// Test that a certificate chain with intermediate may be sent
// and received as both client and server.
testCases = append(testCases, testCase{
testType: clientTest,
name: "SendReceiveIntermediate-Client-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaChainCertificate,
ClientAuth: RequireAnyClientCert,
},
expectations: connectionExpectations{
peerCertificate: &rsaChainCertificate,
},
shimCertificate: &rsaChainCertificate,
flags: []string{
"-expect-peer-cert-file", rsaChainCertificate.ChainPath,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SendReceiveIntermediate-Server-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaChainCertificate,
},
expectations: connectionExpectations{
peerCertificate: &rsaChainCertificate,
},
shimCertificate: &rsaChainCertificate,
flags: []string{
"-require-any-client-certificate",
"-expect-peer-cert-file", rsaChainCertificate.ChainPath,
},
})
// Test that garbage leaf certificates are properly rejected.
testCases = append(testCases, testCase{
testType: clientTest,
name: "GarbageCertificate-Client-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &garbageCertificate,
},
shouldFail: true,
expectedError: ":CANNOT_PARSE_LEAF_CERT:",
expectedLocalError: "remote error: error decoding message",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "GarbageCertificate-Server-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &garbageCertificate,
},
flags: []string{"-require-any-client-certificate"},
shouldFail: true,
expectedError: ":CANNOT_PARSE_LEAF_CERT:",
expectedLocalError: "remote error: error decoding message",
})
}
}
func addRetainOnlySHA256ClientCertTests() {
for _, ver := range tlsVersions {
// Test that enabling
// SSL_CTX_set_retain_only_sha256_of_client_certs without
// actually requesting a client certificate is a no-op.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-NoCert-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
flags: []string{
"-on-initial-retain-only-sha256-client-cert",
"-on-resume-retain-only-sha256-client-cert",
},
resumeSession: true,
})
// Test that when retaining only a SHA-256 certificate is
// enabled, the hash appears as expected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-Cert-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
},
flags: []string{
"-verify-peer",
"-on-initial-retain-only-sha256-client-cert",
"-on-resume-retain-only-sha256-client-cert",
"-on-initial-expect-sha256-client-cert",
"-on-resume-expect-sha256-client-cert",
},
resumeSession: true,
})
// Test that when the config changes from on to off, a
// resumption is rejected because the server now wants the full
// certificate chain.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-OnOff-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
},
flags: []string{
"-verify-peer",
"-on-initial-retain-only-sha256-client-cert",
"-on-initial-expect-sha256-client-cert",
},
resumeSession: true,
expectResumeRejected: true,
})
// Test that when the config changes from off to on, a
// resumption is rejected because the server now wants just the
// hash.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RetainOnlySHA256-OffOn-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rsaCertificate,
},
flags: []string{
"-verify-peer",
"-on-resume-retain-only-sha256-client-cert",
"-on-resume-expect-sha256-client-cert",
},
resumeSession: true,
expectResumeRejected: true,
})
}
}
+360
View File
@@ -0,0 +1,360 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
import "slices"
func addChangeCipherSpecTests() {
// Test missing ChangeCipherSpecs.
testCases = append(testCases, testCase{
name: "SkipChangeCipherSpec-Client",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SkipChangeCipherSpec-Server",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "SkipChangeCipherSpec-Server-NPN",
config: Config{
MaxVersion: VersionTLS12,
NextProtos: []string{"bar"},
Bugs: ProtocolBugs{
SkipChangeCipherSpec: true,
},
},
flags: []string{
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
// Test synchronization between the handshake and ChangeCipherSpec.
// Partial post-CCS handshake messages before ChangeCipherSpec should be
// rejected. Test both with and without handshake packing to handle both
// when the partial post-CCS message is in its own record and when it is
// attached to the pre-CCS message.
for _, packed := range []bool{false, true} {
var suffix string
if packed {
suffix = "-Packed"
}
testCases = append(testCases, testCase{
name: "FragmentAcrossChangeCipherSpec-Client" + suffix,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
PackHandshakeFlight: packed,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
name: "FragmentAcrossChangeCipherSpec-Client-Resume" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
resumeSession: true,
resumeConfig: &Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
PackHandshakeFlight: packed,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "FragmentAcrossChangeCipherSpec-Server" + suffix,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
PackHandshakeFlight: packed,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "FragmentAcrossChangeCipherSpec-Server-Resume" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
resumeSession: true,
resumeConfig: &Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
PackHandshakeFlight: packed,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "FragmentAcrossChangeCipherSpec-Server-NPN" + suffix,
config: Config{
MaxVersion: VersionTLS12,
NextProtos: []string{"bar"},
Bugs: ProtocolBugs{
FragmentAcrossChangeCipherSpec: true,
PackHandshakeFlight: packed,
},
},
flags: []string{
"-advertise-npn", "\x03foo\x03bar\x03baz",
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
}
// In TLS 1.2 resumptions, the client sends ClientHello in the first flight
// and ChangeCipherSpec + Finished in the second flight. Test the server's
// behavior when the Finished message is fragmented across not only
// ChangeCipherSpec but also the flight boundary.
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialClientFinishedWithClientHello-TLS12-Resume",
config: Config{
MaxVersion: VersionTLS12,
},
resumeConfig: &Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
PartialClientFinishedWithClientHello: true,
},
},
resumeSession: true,
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// In TLS 1.2 full handshakes without tickets, the server's first flight ends
// with ServerHelloDone and the second flight is ChangeCipherSpec + Finished.
// Test the client's behavior when the Finished message is fragmented across
// not only ChangeCipherSpec but also the flight boundary.
testCases = append(testCases, testCase{
testType: clientTest,
name: "PartialFinishedWithServerHelloDone",
config: Config{
MaxVersion: VersionTLS12,
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
PartialFinishedWithServerHelloDone: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// Test that, in DTLS 1.2, key changes are not allowed when there are
// buffered messages. Do this sending all messages in reverse, so that later
// ones are buffered, and leaving Finished unencrypted.
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "KeyChangeWithBufferedMessages-DTLS",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
next = slices.Clone(next)
slices.Reverse(next)
for i := range next {
next[i].Epoch = 0
}
c.WriteFlight(next)
},
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
})
// Test synchronization between encryption changes and the handshake in
// TLS 1.3, where ChangeCipherSpec is implicit.
testCases = append(testCases, testCase{
name: "PartialEncryptedExtensionsWithServerHello",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
PartialEncryptedExtensionsWithServerHello: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialClientFinishedWithClientHello",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
PartialClientFinishedWithClientHello: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialClientFinishedWithSecondClientHello",
config: Config{
MaxVersion: VersionTLS13,
// Trigger a curve-based HelloRetryRequest.
DefaultCurves: []CurveID{},
Bugs: ProtocolBugs{
PartialClientFinishedWithSecondClientHello: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialEndOfEarlyDataWithClientHello",
config: Config{
MaxVersion: VersionTLS13,
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
PartialEndOfEarlyDataWithClientHello: true,
},
},
resumeSession: true,
earlyData: true,
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
})
// Test that early ChangeCipherSpecs are handled correctly.
testCases = append(testCases, testCase{
testType: serverTest,
name: "EarlyChangeCipherSpec-server-1",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
EarlyChangeCipherSpec: 1,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "EarlyChangeCipherSpec-server-2",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
EarlyChangeCipherSpec: 2,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_RECORD:",
})
testCases = append(testCases, testCase{
protocol: dtls,
name: "StrayChangeCipherSpec",
config: Config{
// TODO(davidben): Once DTLS 1.3 exists, test
// that stray ChangeCipherSpec messages are
// rejected.
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
c.WriteFragments([]DTLSFragment{{IsChangeCipherSpec: true, Data: []byte{1}}})
c.WriteFlight(next)
},
},
},
})
// Test that the contents of ChangeCipherSpec are checked.
testCases = append(testCases, testCase{
name: "BadChangeCipherSpec-1",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadChangeCipherSpec: []byte{2},
},
},
shouldFail: true,
expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
})
testCases = append(testCases, testCase{
name: "BadChangeCipherSpec-2",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadChangeCipherSpec: []byte{1, 1},
},
},
shouldFail: true,
expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
})
testCases = append(testCases, testCase{
protocol: dtls,
name: "BadChangeCipherSpec-DTLS-1",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadChangeCipherSpec: []byte{2},
},
},
shouldFail: true,
expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
})
testCases = append(testCases, testCase{
protocol: dtls,
name: "BadChangeCipherSpec-DTLS-2",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadChangeCipherSpec: []byte{1, 1},
},
},
shouldFail: true,
expectedError: ":BAD_CHANGE_CIPHER_SPEC:",
})
}
+575
View File
@@ -0,0 +1,575 @@
// 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"
"strconv"
"strings"
)
type testCipherSuite struct {
name string
id uint16
}
var testCipherSuites = []testCipherSuite{
{"RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
{"RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
{"RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
{"RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
{"RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
{"ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
{"ECDHE_ECDSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
{"ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
{"ECDHE_ECDSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
{"ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
{"ECDHE_RSA_WITH_AES_128_GCM_SHA256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
{"ECDHE_RSA_WITH_AES_128_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
{"ECDHE_RSA_WITH_AES_128_CBC_SHA256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
{"ECDHE_RSA_WITH_AES_256_GCM_SHA384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
{"ECDHE_RSA_WITH_AES_256_CBC_SHA", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
{"ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
{"PSK_WITH_AES_128_CBC_SHA", TLS_PSK_WITH_AES_128_CBC_SHA},
{"PSK_WITH_AES_256_CBC_SHA", TLS_PSK_WITH_AES_256_CBC_SHA},
{"ECDHE_PSK_WITH_AES_128_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
{"ECDHE_PSK_WITH_AES_256_CBC_SHA", TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA},
{"ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256", TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256},
{"CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
{"AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
{"AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
}
func hasComponent(suiteName, component string) bool {
return strings.Contains("_"+suiteName+"_", "_"+component+"_")
}
func isTLS12Only(suiteName string) bool {
return hasComponent(suiteName, "GCM") ||
hasComponent(suiteName, "SHA256") ||
hasComponent(suiteName, "SHA384") ||
hasComponent(suiteName, "POLY1305")
}
func isTLS13Suite(suiteName string) bool {
return !hasComponent(suiteName, "WITH")
}
func addTestForCipherSuite(suite testCipherSuite, ver tlsVersion, protocol protocol) {
const psk = "12345"
const pskIdentity = "luggage combo"
if !ver.supportsProtocol(protocol) {
return
}
prefix := protocol.String() + "-"
var cert *Credential
if isTLS13Suite(suite.name) {
cert = &rsaCertificate
} else if hasComponent(suite.name, "ECDSA") {
cert = &ecdsaP256Certificate
} else if hasComponent(suite.name, "RSA") {
cert = &rsaCertificate
}
var flags []string
if hasComponent(suite.name, "PSK") {
flags = append(flags,
"-psk", psk,
"-psk-identity", pskIdentity)
}
if hasComponent(suite.name, "3DES") {
// BoringSSL disables 3DES ciphers by default.
flags = append(flags, "-cipher", "3DES")
}
var shouldFail bool
if isTLS12Only(suite.name) && ver.version < VersionTLS12 {
shouldFail = true
}
if !isTLS13Suite(suite.name) && ver.version >= VersionTLS13 {
shouldFail = true
}
if isTLS13Suite(suite.name) && ver.version < VersionTLS13 {
shouldFail = true
}
var sendCipherSuite uint16
var expectedServerError, expectedClientError string
serverCipherSuites := []uint16{suite.id}
if shouldFail {
expectedServerError = ":NO_SHARED_CIPHER:"
if ver.version >= VersionTLS13 && cert == nil {
// TLS 1.2 PSK ciphers won't configure a server certificate, but we
// require one in TLS 1.3.
expectedServerError = ":NO_CERTIFICATE_SET:"
}
expectedClientError = ":WRONG_CIPHER_RETURNED:"
// Configure the server to select ciphers as normal but
// select an incompatible cipher in ServerHello.
serverCipherSuites = nil
sendCipherSuite = suite.id
}
// Verify exporters interoperate.
exportKeyingMaterial := 1024
if ver.version != VersionTLS13 || !ver.hasDTLS {
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: prefix + ver.name + "-" + suite.name + "-server",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Credential: cert,
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
Bugs: ProtocolBugs{
AdvertiseAllConfiguredCiphers: true,
},
},
shimCertificate: cert,
flags: flags,
resumeSession: true,
shouldFail: shouldFail,
expectedError: expectedServerError,
exportKeyingMaterial: exportKeyingMaterial,
})
testCases = append(testCases, testCase{
testType: clientTest,
protocol: protocol,
name: prefix + ver.name + "-" + suite.name + "-client",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: serverCipherSuites,
Credential: cert,
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
Bugs: ProtocolBugs{
IgnorePeerCipherPreferences: shouldFail,
SendCipherSuite: sendCipherSuite,
},
},
flags: flags,
resumeSession: true,
shouldFail: shouldFail,
expectedError: expectedClientError,
exportKeyingMaterial: exportKeyingMaterial,
})
}
if shouldFail {
return
}
// Ensure the maximum record size is accepted.
testCases = append(testCases, testCase{
protocol: protocol,
name: prefix + ver.name + "-" + suite.name + "-LargeRecord",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Credential: cert,
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
flags: flags,
messageLen: maxPlaintext,
})
// Test bad records for all ciphers. Bad records are fatal in TLS
// and ignored in DTLS.
shouldFail = protocol == tls
var expectedError string
if shouldFail {
expectedError = ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:"
}
// When QUIC is used, the QUIC stack handles record encryption/decryption.
// Thus it is not possible for the TLS stack in QUIC mode to receive a
// bad record (i.e. one that fails to decrypt).
if protocol != quic {
testCases = append(testCases, testCase{
protocol: protocol,
name: prefix + ver.name + "-" + suite.name + "-BadRecord",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
CipherSuites: []uint16{suite.id},
Credential: cert,
PreSharedKey: []byte(psk),
PreSharedKeyIdentity: pskIdentity,
},
flags: flags,
damageFirstWrite: true,
messageLen: maxPlaintext,
shouldFail: shouldFail,
expectedError: expectedError,
})
}
}
func addCipherSuiteTests() {
const bogusCipher = 0xfe00
for _, suite := range testCipherSuites {
for _, ver := range tlsVersions {
for _, protocol := range []protocol{tls, dtls, quic} {
addTestForCipherSuite(suite, ver, protocol)
}
}
}
testCases = append(testCases, testCase{
name: "NoSharedCipher",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{},
},
shouldFail: true,
expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
})
testCases = append(testCases, testCase{
name: "NoSharedCipher-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{},
},
shouldFail: true,
expectedError: ":HANDSHAKE_FAILURE_ON_CLIENT_HELLO:",
})
testCases = append(testCases, testCase{
name: "UnsupportedCipherSuite",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
IgnorePeerCipherPreferences: true,
},
},
flags: []string{"-cipher", "DEFAULT:!AES"},
shouldFail: true,
expectedError: ":WRONG_CIPHER_RETURNED:",
})
testCases = append(testCases, testCase{
name: "ServerHelloBogusCipher",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendCipherSuite: bogusCipher,
},
},
shouldFail: true,
expectedError: ":WRONG_CIPHER_RETURNED:",
})
testCases = append(testCases, testCase{
name: "ServerHelloBogusCipher-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendCipherSuite: bogusCipher,
},
},
shouldFail: true,
expectedError: ":WRONG_CIPHER_RETURNED:",
})
// The server must be tolerant to bogus ciphers.
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnknownCipher",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{bogusCipher, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
AdvertiseAllConfiguredCiphers: true,
},
},
})
// The server must be tolerant to bogus ciphers.
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnknownCipher-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{bogusCipher, TLS_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
AdvertiseAllConfiguredCiphers: true,
},
},
})
// Test empty ECDHE_PSK identity hints work as expected.
testCases = append(testCases, testCase{
name: "EmptyECDHEPSKHint",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
},
flags: []string{"-psk", "secret"},
})
// Test empty PSK identity hints work as expected, even if an explicit
// ServerKeyExchange is sent.
testCases = append(testCases, testCase{
name: "ExplicitEmptyPSKHint",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_PSK_WITH_AES_128_CBC_SHA},
PreSharedKey: []byte("secret"),
Bugs: ProtocolBugs{
AlwaysSendPreSharedKeyIdentityHint: true,
},
},
flags: []string{"-psk", "secret"},
})
// Test that clients enforce that the server-sent certificate and cipher
// suite match in TLS 1.2.
testCases = append(testCases, testCase{
name: "CertificateCipherMismatch-RSA",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Credential: &rsaCertificate,
Bugs: ProtocolBugs{
SendCipherSuite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
},
},
shouldFail: true,
expectedError: ":WRONG_CERTIFICATE_TYPE:",
})
testCases = append(testCases, testCase{
name: "CertificateCipherMismatch-ECDSA",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
Credential: &ecdsaP256Certificate,
Bugs: ProtocolBugs{
SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
shouldFail: true,
expectedError: ":WRONG_CERTIFICATE_TYPE:",
})
testCases = append(testCases, testCase{
name: "CertificateCipherMismatch-Ed25519",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
Credential: &ed25519Certificate,
Bugs: ProtocolBugs{
SendCipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
},
shouldFail: true,
expectedError: ":WRONG_CERTIFICATE_TYPE:",
})
// Test that servers decline to select a cipher suite which is
// inconsistent with their configured certificate.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ServerCipherFilter-RSA",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
},
shimCertificate: &rsaCertificate,
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ServerCipherFilter-ECDSA",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
shimCertificate: &ecdsaP256Certificate,
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ServerCipherFilter-Ed25519",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
shimCertificate: &ed25519Certificate,
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
})
// Test cipher suite negotiation works as expected. Configure a
// complicated cipher suite configuration.
const negotiationTestCiphers = "" +
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:" +
"[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:" +
"TLS_RSA_WITH_AES_128_GCM_SHA256:" +
"TLS_RSA_WITH_AES_128_CBC_SHA:" +
"[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]"
negotiationTests := []struct {
ciphers []uint16
expected uint16
}{
// Server preferences are honored, including when
// equipreference groups are involved.
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
},
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
},
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_128_GCM_SHA256,
},
TLS_RSA_WITH_AES_128_GCM_SHA256,
},
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
},
TLS_RSA_WITH_AES_128_CBC_SHA,
},
// Equipreference groups use the client preference.
{
[]uint16{
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
},
{
[]uint16{
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
{
[]uint16{
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_256_CBC_SHA,
},
TLS_RSA_WITH_AES_256_GCM_SHA384,
},
{
[]uint16{
TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_RSA_WITH_AES_256_GCM_SHA384,
},
TLS_RSA_WITH_AES_256_CBC_SHA,
},
// If there are two equipreference groups, the preferred one
// takes precedence.
{
[]uint16{
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_256_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
},
}
for i, t := range negotiationTests {
testCases = append(testCases, testCase{
testType: serverTest,
name: "CipherNegotiation-" + strconv.Itoa(i),
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: t.ciphers,
},
flags: []string{"-cipher", negotiationTestCiphers},
expectations: connectionExpectations{
cipher: t.expected,
},
})
}
}
func addRSAClientKeyExchangeTests() {
for bad := RSABadValue(1); bad < NumRSABadValues; bad++ {
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("BadRSAClientKeyExchange-%d", bad),
config: Config{
// Ensure the ClientHello version and final
// version are different, to detect if the
// server uses the wrong one.
MaxVersion: VersionTLS11,
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
BadRSAClientKeyExchange: bad,
},
},
shouldFail: true,
expectedError: ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:",
})
}
// The server must compare whatever was in ClientHello.version for the
// RSA premaster.
testCases = append(testCases, testCase{
testType: serverTest,
name: "SendClientVersion-RSA",
config: Config{
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SendClientVersion: 0x1234,
},
},
flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
})
}
+290
View File
@@ -0,0 +1,290 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addCompliancePolicyTests() {
for _, protocol := range []protocol{tls, quic} {
for _, suite := range testCipherSuites {
var isFIPSCipherSuite bool
switch suite.id {
case TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
isFIPSCipherSuite = true
}
var isWPACipherSuite bool
switch suite.id {
case TLS_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
isWPACipherSuite = true
}
var cert Credential
if hasComponent(suite.name, "ECDSA") {
cert = ecdsaP384Certificate
} else {
cert = rsaCertificate
}
maxVersion := uint16(VersionTLS13)
if !isTLS13Suite(suite.name) {
if protocol == quic {
continue
}
maxVersion = VersionTLS12
}
policies := []struct {
flag string
cipherSuiteOk bool
}{
{"-fips-202205", isFIPSCipherSuite},
{"-wpa-202304", isWPACipherSuite},
}
for _, policy := range policies {
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + suite.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: maxVersion,
CipherSuites: []uint16{suite.id},
},
shimCertificate: &cert,
flags: []string{
policy.flag,
},
shouldFail: !policy.cipherSuiteOk,
})
testCases = append(testCases, testCase{
testType: clientTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + suite.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: maxVersion,
CipherSuites: []uint16{suite.id},
Credential: &cert,
},
flags: []string{
policy.flag,
},
shouldFail: !policy.cipherSuiteOk,
})
}
}
// Check that a TLS 1.3 client won't accept ChaCha20 even if the server
// picks it without it being in the client's cipher list.
testCases = append(testCases, testCase{
testType: clientTest,
protocol: protocol,
name: "Compliance-fips202205-" + protocol.String() + "-Client-ReallyWontAcceptChaCha",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: maxVersion,
Bugs: ProtocolBugs{
SendCipherSuite: TLS_CHACHA20_POLY1305_SHA256,
},
},
flags: []string{
"-fips-202205",
},
shouldFail: true,
expectedError: ":WRONG_CIPHER_RETURNED:",
})
for _, curve := range testCurves {
var isFIPSCurve bool
switch curve.id {
case CurveP256, CurveP384:
isFIPSCurve = true
}
var isWPACurve bool
switch curve.id {
case CurveP384:
isWPACurve = true
}
policies := []struct {
flag string
curveOk bool
}{
{"-fips-202205", isFIPSCurve},
{"-wpa-202304", isWPACurve},
}
for _, policy := range policies {
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + curve.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{curve.id},
},
flags: []string{
policy.flag,
},
shouldFail: !policy.curveOk,
})
testCases = append(testCases, testCase{
testType: clientTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + curve.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{curve.id},
},
flags: []string{
policy.flag,
},
shouldFail: !policy.curveOk,
})
}
}
for _, sigalg := range testSignatureAlgorithms {
// The TLS 1.0 and TLS 1.1 default signature algorithm does not
// apply to these tests.
if sigalg.id == 0 {
continue
}
var isFIPSSigAlg bool
switch sigalg.id {
case signatureRSAPKCS1WithSHA256,
signatureRSAPKCS1WithSHA384,
signatureRSAPKCS1WithSHA512,
signatureECDSAWithP256AndSHA256,
signatureECDSAWithP384AndSHA384,
signatureRSAPSSWithSHA256,
signatureRSAPSSWithSHA384,
signatureRSAPSSWithSHA512:
isFIPSSigAlg = true
}
var isWPASigAlg bool
switch sigalg.id {
case signatureRSAPKCS1WithSHA384,
signatureRSAPKCS1WithSHA512,
signatureECDSAWithP384AndSHA384,
signatureRSAPSSWithSHA384,
signatureRSAPSSWithSHA512:
isWPASigAlg = true
}
if sigalg.curve == CurveP224 {
// This can work in TLS 1.2, but not with TLS 1.3.
// For consistency it's not permitted in FIPS mode.
isFIPSSigAlg = false
}
maxVersion := uint16(VersionTLS13)
if hasComponent(sigalg.name, "PKCS1") {
if protocol == quic {
continue
}
maxVersion = VersionTLS12
}
policies := []struct {
flag string
sigAlgOk bool
}{
{"-fips-202205", isFIPSSigAlg},
{"-wpa-202304", isWPASigAlg},
}
cert := sigalg.baseCert.WithSignatureAlgorithms(sigalg.id)
for _, policy := range policies {
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Server-" + sigalg.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: maxVersion,
VerifySignatureAlgorithms: []signatureAlgorithm{sigalg.id},
},
// Use the base certificate. We wish to pick up the signature algorithm
// preferences from the FIPS policy.
shimCertificate: sigalg.baseCert,
flags: []string{policy.flag},
shouldFail: !policy.sigAlgOk,
})
testCases = append(testCases, testCase{
testType: clientTest,
protocol: protocol,
name: "Compliance" + policy.flag + "-" + protocol.String() + "-Client-" + sigalg.name,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: maxVersion,
Credential: cert,
},
flags: []string{
policy.flag,
},
shouldFail: !policy.sigAlgOk,
})
}
}
// AES-256-GCM is the most preferred.
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "Compliance-cnsa202407-" + protocol.String() + "-AES-256-preferred",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384},
},
flags: []string{
"-cnsa-202407",
},
expectations: connectionExpectations{cipher: TLS_AES_256_GCM_SHA384},
})
// AES-128-GCM is preferred over ChaCha20-Poly1305.
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: "Compliance-cnsa202407-" + protocol.String() + "-AES-128-preferred",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
},
flags: []string{
"-cnsa-202407",
},
expectations: connectionExpectations{cipher: TLS_AES_128_GCM_SHA256},
})
}
}
+736
View File
@@ -0,0 +1,736 @@
// 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"
"strconv"
)
var testCurves = []struct {
name string
id CurveID
}{
{"P-224", CurveP224},
{"P-256", CurveP256},
{"P-384", CurveP384},
{"P-521", CurveP521},
{"X25519", CurveX25519},
{"Kyber", CurveX25519Kyber768},
{"MLKEM", CurveX25519MLKEM768},
}
const bogusCurve = 0x1234
func isPqGroup(r CurveID) bool {
return r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
}
func isECDHGroup(r CurveID) bool {
return r == CurveP224 || r == CurveP256 || r == CurveP384 || r == CurveP521
}
func isX25519Group(r CurveID) bool {
return r == CurveX25519 || r == CurveX25519Kyber768 || r == CurveX25519MLKEM768
}
func addCurveTests() {
// A set of cipher suites that ensures some curve-using mode is used.
// Without this, servers may fall back to RSA key exchange.
ecdheCiphers := []uint16{
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_AES_256_GCM_SHA384,
}
for _, curve := range testCurves {
for _, ver := range tlsVersions {
if isPqGroup(curve.id) && ver.version < VersionTLS13 {
continue
}
for _, testType := range []testType{clientTest, serverTest} {
suffix := fmt.Sprintf("%s-%s-%s", testType, curve.name, ver.name)
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
},
flags: append(
[]string{"-expect-curve-id", strconv.Itoa(int(curve.id))},
flagInts("-curves", shimConfig.AllCurves)...,
),
expectations: connectionExpectations{
curveID: curve.id,
},
})
badKeyShareLocalError := "remote error: illegal parameter"
if testType == clientTest && ver.version >= VersionTLS13 {
// If the shim is a TLS 1.3 client and the runner sends a bad
// key share, the runner never reads the client's cleartext
// alert because the runner has already started encrypting by
// the time the client sees it.
badKeyShareLocalError = "local error: bad record MAC"
}
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-TruncateKeyShare-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
TruncateKeyShare: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-PadKeyShare-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
PadKeyShare: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
if isECDHGroup(curve.id) {
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-Compressed-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
SendCompressedCoordinates: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-NotOnCurve-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
ECDHPointNotOnCurve: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
}
if isX25519Group(curve.id) {
// Implementations should mask off the high order bit in X25519.
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-SetX25519HighBit-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
SetX25519HighBit: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
expectations: connectionExpectations{
curveID: curve.id,
},
})
// Implementations should reject low order points.
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-LowOrderX25519Point-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
LowOrderX25519Point: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
}
if curve.id == CurveX25519MLKEM768 && testType == serverTest {
testCases = append(testCases, testCase{
testType: testType,
name: "CurveTest-Invalid-MLKEMEncapKeyNotReduced-" + suffix,
config: Config{
MaxVersion: ver.version,
CipherSuites: ecdheCiphers,
CurvePreferences: []CurveID{curve.id},
Bugs: ProtocolBugs{
MLKEMEncapKeyNotReduced: true,
},
},
flags: flagInts("-curves", shimConfig.AllCurves),
shouldFail: true,
expectedError: ":BAD_ECPOINT:",
expectedLocalError: badKeyShareLocalError,
})
}
}
}
}
// The server must be tolerant to bogus curves.
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnknownCurve",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{bogusCurve, CurveP256},
},
})
// The server must be tolerant to bogus curves.
testCases = append(testCases, testCase{
testType: serverTest,
name: "UnknownCurve-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{bogusCurve, CurveP256},
},
})
// The server must not consider ECDHE ciphers when there are no
// supported curves.
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSupportedCurves",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
NoSupportedCurves: true,
},
},
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSupportedCurves-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
NoSupportedCurves: true,
},
},
shouldFail: true,
expectedError: ":NO_SHARED_GROUP:",
})
// The server must fall back to another cipher when there are no
// supported curves.
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoCommonCurves",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_128_GCM_SHA256,
},
CurvePreferences: []CurveID{CurveP224},
},
expectations: connectionExpectations{
cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
},
})
// The client must reject bogus curves and disabled curves.
testCases = append(testCases, testCase{
name: "BadECDHECurve",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SendCurve: bogusCurve,
},
},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
})
testCases = append(testCases, testCase{
name: "BadECDHECurve-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendCurve: bogusCurve,
},
},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
})
testCases = append(testCases, testCase{
name: "UnsupportedCurve",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{CurveP256},
Bugs: ProtocolBugs{
IgnorePeerCurvePreferences: true,
},
},
flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
})
testCases = append(testCases, testCase{
// TODO(davidben): Add a TLS 1.3 version where
// HelloRetryRequest requests an unsupported curve.
name: "UnsupportedCurve-ServerHello-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveP384},
Bugs: ProtocolBugs{
SendCurve: CurveP256,
},
},
flags: []string{"-curves", strconv.Itoa(int(CurveP384))},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
})
// The previous curve ID should be reported on TLS 1.2 resumption.
testCases = append(testCases, testCase{
name: "CurveID-Resume-Client",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{CurveX25519},
},
flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "CurveID-Resume-Server",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
CurvePreferences: []CurveID{CurveX25519},
},
flags: []string{"-expect-curve-id", strconv.Itoa(int(CurveX25519))},
resumeSession: true,
})
// TLS 1.3 allows resuming at a differet curve. If this happens, the new
// one should be reported.
testCases = append(testCases, testCase{
name: "CurveID-Resume-Client-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveX25519},
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveP256},
},
flags: []string{
"-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
"-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
},
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "CurveID-Resume-Server-TLS13",
config: Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveX25519},
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveP256},
},
flags: []string{
"-on-initial-expect-curve-id", strconv.Itoa(int(CurveX25519)),
"-on-resume-expect-curve-id", strconv.Itoa(int(CurveP256)),
},
resumeSession: true,
})
// Server-sent point formats are legal in TLS 1.2, but not in TLS 1.3.
testCases = append(testCases, testCase{
name: "PointFormat-ServerHello-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{pointFormatUncompressed},
},
},
})
testCases = append(testCases, testCase{
name: "PointFormat-EncryptedExtensions-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{pointFormatUncompressed},
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
// Server-sent supported groups/curves are legal in TLS 1.3. They are
// illegal in TLS 1.2, but some servers send them anyway, so we must
// tolerate them.
testCases = append(testCases, testCase{
name: "SupportedCurves-ServerHello-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendServerSupportedCurves: true,
},
},
})
testCases = append(testCases, testCase{
name: "SupportedCurves-EncryptedExtensions-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendServerSupportedCurves: true,
},
},
})
// Test that we tolerate unknown point formats, as long as
// pointFormatUncompressed is present. Limit ciphers to ECDHE ciphers to
// check they are still functional.
testCases = append(testCases, testCase{
name: "PointFormat-Client-Tolerance",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
},
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PointFormat-Server-Tolerance",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{42, pointFormatUncompressed, 99, pointFormatCompressedPrime},
},
},
})
// Test TLS 1.2 does not require the point format extension to be
// present.
testCases = append(testCases, testCase{
name: "PointFormat-Client-Missing",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{},
},
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PointFormat-Server-Missing",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{},
},
},
})
// If the point format extension is present, uncompressed points must be
// offered. BoringSSL requires this whether or not ECDHE is used.
testCases = append(testCases, testCase{
name: "PointFormat-Client-MissingUncompressed",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "PointFormat-Server-MissingUncompressed",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendSupportedPointFormats: []byte{pointFormatCompressedPrime},
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
// Post-quantum groups require TLS 1.3.
for _, curve := range testCurves {
if !isPqGroup(curve.id) {
continue
}
// Post-quantum groups should not be offered by a TLS 1.2 client.
testCases = append(testCases, testCase{
name: "TLS12ClientShouldNotOffer-" + curve.name,
config: Config{
Bugs: ProtocolBugs{
FailIfPostQuantumOffered: true,
},
},
flags: []string{
"-max-version", strconv.Itoa(VersionTLS12),
"-curves", strconv.Itoa(int(curve.id)),
"-curves", strconv.Itoa(int(CurveX25519)),
},
})
// Post-quantum groups should not be selected by a TLS 1.2 server.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS12ServerShouldNotSelect-" + curve.name,
flags: []string{
"-max-version", strconv.Itoa(VersionTLS12),
"-curves", strconv.Itoa(int(curve.id)),
"-curves", strconv.Itoa(int(CurveX25519)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
// If a TLS 1.2 server selects a post-quantum group anyway, the client
// should not accept it.
testCases = append(testCases, testCase{
name: "ClientShouldNotAllowInTLS12-" + curve.name,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendCurve: curve.id,
},
},
flags: []string{
"-curves", strconv.Itoa(int(curve.id)),
"-curves", strconv.Itoa(int(CurveX25519)),
},
shouldFail: true,
expectedError: ":WRONG_CURVE:",
expectedLocalError: "remote error: illegal parameter",
})
}
// ML-KEM and Kyber should not be offered by default as a client.
testCases = append(testCases, testCase{
name: "PostQuantumNotEnabledByDefaultInClients",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
FailIfPostQuantumOffered: true,
},
},
})
// If ML-KEM is offered, both X25519 and ML-KEM should have a key-share.
testCases = append(testCases, testCase{
name: "NotJustMLKEMKeyShare",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectedKeyShares: []CurveID{CurveX25519MLKEM768, CurveX25519},
},
},
flags: []string{
"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
"-curves", strconv.Itoa(int(CurveX25519)),
"-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
},
})
// ... and the other way around
testCases = append(testCases, testCase{
name: "MLKEMKeyShareIncludedSecond",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
},
},
flags: []string{
"-curves", strconv.Itoa(int(CurveX25519)),
"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
},
})
// ... and even if there's another curve in the middle because it's the
// first classical and first post-quantum "curves" that get key shares
// included.
testCases = append(testCases, testCase{
name: "MLKEMKeyShareIncludedThird",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectedKeyShares: []CurveID{CurveX25519, CurveX25519MLKEM768},
},
},
flags: []string{
"-curves", strconv.Itoa(int(CurveX25519)),
"-curves", strconv.Itoa(int(CurveP256)),
"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
},
})
// If ML-KEM is the only configured curve, the key share is sent.
testCases = append(testCases, testCase{
name: "JustConfiguringMLKEMWorks",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
},
},
flags: []string{
"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
"-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
},
})
// If both ML-KEM and Kyber are configured, only the preferred one's
// key share should be sent.
testCases = append(testCases, testCase{
name: "BothMLKEMAndKyber",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectedKeyShares: []CurveID{CurveX25519MLKEM768},
},
},
flags: []string{
"-curves", strconv.Itoa(int(CurveX25519MLKEM768)),
"-curves", strconv.Itoa(int(CurveX25519Kyber768)),
"-expect-curve-id", strconv.Itoa(int(CurveX25519MLKEM768)),
},
})
// As a server, ML-KEM is not yet supported by default.
testCases = append(testCases, testCase{
testType: serverTest,
name: "PostQuantumNotEnabledByDefaultForAServer",
config: Config{
MinVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768, CurveX25519},
DefaultCurves: []CurveID{CurveX25519MLKEM768, CurveX25519Kyber768},
},
flags: []string{
"-server-preference",
"-expect-curve-id", strconv.Itoa(int(CurveX25519)),
},
})
// In TLS 1.2, the curve list is also used to signal ECDSA curves.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CheckECDSACurve-TLS12",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
CurvePreferences: []CurveID{CurveP384},
},
shimCertificate: &ecdsaP256Certificate,
shouldFail: true,
expectedError: ":WRONG_CURVE:",
})
// If the ECDSA certificate is ineligible due to a curve mismatch, the
// server may still consider a PSK cipher suite.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CheckECDSACurve-PSK-TLS12",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
CipherSuites: []uint16{
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
},
CurvePreferences: []CurveID{CurveP384},
PreSharedKey: []byte("12345"),
PreSharedKeyIdentity: "luggage combo",
},
shimCertificate: &ecdsaP256Certificate,
expectations: connectionExpectations{
cipher: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
},
flags: []string{
"-psk", "12345",
"-psk-identity", "luggage combo",
},
})
// In TLS 1.3, the curve list only controls ECDH.
testCases = append(testCases, testCase{
testType: serverTest,
name: "CheckECDSACurve-NotApplicable-TLS13",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CurvePreferences: []CurveID{CurveP384},
},
shimCertificate: &ecdsaP256Certificate,
})
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addDDoSCallbackTests() {
// DDoS callback.
for _, resume := range []bool{false, true} {
suffix := "Resume"
if resume {
suffix = "No" + suffix
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "Server-DDoS-OK-" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
flags: []string{"-install-ddos-callback"},
resumeSession: resume,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Server-DDoS-OK-" + suffix + "-TLS13",
config: Config{
MaxVersion: VersionTLS13,
},
flags: []string{"-install-ddos-callback"},
resumeSession: resume,
})
failFlag := "-fail-ddos-callback"
if resume {
failFlag = "-on-resume-fail-ddos-callback"
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "Server-DDoS-Reject-" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
flags: []string{"-install-ddos-callback", failFlag},
resumeSession: resume,
shouldFail: true,
expectedError: ":CONNECTION_REJECTED:",
expectedLocalError: "remote error: internal error",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Server-DDoS-Reject-" + suffix + "-TLS13",
config: Config{
MaxVersion: VersionTLS13,
},
flags: []string{"-install-ddos-callback", failFlag},
resumeSession: resume,
shouldFail: true,
expectedError: ":CONNECTION_REJECTED:",
expectedLocalError: "remote error: internal error",
})
}
}
@@ -0,0 +1,300 @@
// 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"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"fmt"
"time"
"golang.org/x/crypto/cryptobyte"
)
// delegatedCredentialConfig specifies the shape of a delegated credential, not
// including the keys themselves.
type delegatedCredentialConfig struct {
// lifetime is the amount of time, from the notBefore of the parent
// certificate, that the delegated credential is valid for. If zero, then 24
// hours is assumed.
lifetime time.Duration
// dcAlgo is the signature scheme that should be used with this delegated
// credential. If zero, ECDSA with P-256 is assumed.
dcAlgo signatureAlgorithm
// algo is the signature algorithm that the delegated credential itself is
// signed with. Cannot be zero.
algo signatureAlgorithm
}
func createDelegatedCredential(parent *Credential, config delegatedCredentialConfig) *Credential {
if parent.Type != CredentialTypeX509 {
panic("delegated credentials must be issued by X.509 credentials")
}
dcAlgo := config.dcAlgo
if dcAlgo == 0 {
dcAlgo = signatureECDSAWithP256AndSHA256
}
var dcPriv crypto.Signer
switch dcAlgo {
case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
dcPriv = &rsa2048Key
case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
var curve elliptic.Curve
switch dcAlgo {
case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256:
curve = elliptic.P256()
case signatureECDSAWithP384AndSHA384:
curve = elliptic.P384()
case signatureECDSAWithP521AndSHA512:
curve = elliptic.P521()
default:
panic("internal error")
}
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
panic(err)
}
dcPriv = priv
default:
panic(fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo))
}
lifetime := config.lifetime
if lifetime == 0 {
lifetime = 24 * time.Hour
}
lifetimeSecs := int64(lifetime.Seconds())
if lifetimeSecs < 0 || lifetimeSecs > 1<<32 {
panic(fmt.Errorf("lifetime %s is too long to be expressed", lifetime))
}
// https://www.rfc-editor.org/rfc/rfc9345.html#section-4
dc := cryptobyte.NewBuilder(nil)
dc.AddUint32(uint32(lifetimeSecs))
dc.AddUint16(uint16(dcAlgo))
pubBytes, err := x509.MarshalPKIXPublicKey(dcPriv.Public())
if err != nil {
panic(err)
}
addUint24LengthPrefixedBytes(dc, pubBytes)
var dummyConfig Config
parentSignature, err := signMessage(false /* server */, VersionTLS13, parent.PrivateKey, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc.BytesOrPanic(), config.algo, parent.Leaf.Raw))
if err != nil {
panic(err)
}
dc.AddUint16(uint16(config.algo))
addUint16LengthPrefixedBytes(dc, parentSignature)
dcCred := *parent
dcCred.Type = CredentialTypeDelegated
dcCred.DelegatedCredential = dc.BytesOrPanic()
dcCred.PrivateKey = dcPriv
dcCred.KeyPath = writeTempKeyFile(dcPriv)
return &dcCred
}
func addDelegatedCredentialTests() {
p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP256AndSHA256,
algo: signatureRSAPSSWithSHA256,
})
p256DCFromECDSA := createDelegatedCredential(&ecdsaP256Certificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP256AndSHA256,
algo: signatureECDSAWithP256AndSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-NoClientSupport",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-Basic",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{
peerCertificate: p256DC,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-ExactAlgorithmMatch",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// Test that the server doesn't mix up the two signature algorithm
// fields. These options are a match because the signature_algorithms
// extension matches against the signature on the delegated
// credential, while the delegated_credential extension matches
// against the signature made by the delegated credential.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "0"},
expectations: connectionExpectations{
peerCertificate: p256DC,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-SigAlgoMissing",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// If the client doesn't support the signature in the delegated credential,
// the server should not use delegated credentials.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA384},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-CertVerifySigAlgoMissing",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// If the client doesn't support the delegated credential's
// CertificateVerify algorithm, the server should not use delegated
// credentials.
VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
// Delegated credentials are not supported at TLS 1.2, even if the client
// sends the extension.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-TLS12-Forbidden",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
},
shimCredentials: []*Credential{p256DC, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: &rsaCertificate,
},
})
// Generate another delegated credential, so we can get the keys out of sync.
dcWrongKey := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
algo: signatureRSAPSSWithSHA256,
})
dcWrongKey.DelegatedCredential = p256DC.DelegatedCredential
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-KeyMismatch",
// The handshake hints version of the test will, as a side effect, use a
// custom private key. Custom private keys can't be checked for key
// mismatches.
skipHints: true,
shimCredentials: []*Credential{dcWrongKey},
shouldFail: true,
expectedError: ":KEY_VALUES_MISMATCH:",
})
// RSA delegated credentials should be rejected at configuration time.
rsaDC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
algo: signatureRSAPSSWithSHA256,
dcAlgo: signatureRSAPSSWithSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-NoRSA",
shimCredentials: []*Credential{rsaDC},
shouldFail: true,
expectedError: ":INVALID_SIGNATURE_ALGORITHM:",
})
// If configured with multiple delegated credentials, the server can cleanly
// select the first one that works.
p384DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
dcAlgo: signatureECDSAWithP384AndSHA384,
algo: signatureRSAPSSWithSHA256,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-Multiple",
config: Config{
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
},
shimCredentials: []*Credential{p256DC, p384DC},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: p384DC,
},
})
// Delegated credentials participate in issuer-based certificate selection.
testCases = append(testCases, testCase{
testType: serverTest,
name: "DelegatedCredentials-MatchIssuer",
config: Config{
DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
// The client requested p256DCFromECDSA's issuer.
RootCAs: makeCertPoolFromRoots(p256DCFromECDSA),
SendRootCAs: true,
},
shimCredentials: []*Credential{
p256DC.WithMustMatchIssuer(true), p256DCFromECDSA.WithMustMatchIssuer(true)},
flags: []string{"-expect-selected-credential", "1"},
expectations: connectionExpectations{
peerCertificate: p256DCFromECDSA,
},
})
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+199
View File
@@ -0,0 +1,199 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addExtendedMasterSecretTests() {
const expectEMSFlag = "-expect-extended-master-secret"
for _, with := range []bool{false, true} {
prefix := "No"
if with {
prefix = ""
}
for _, isClient := range []bool{false, true} {
suffix := "-Server"
testType := serverTest
if isClient {
suffix = "-Client"
testType = clientTest
}
for _, ver := range tlsVersions {
// In TLS 1.3, the extension is irrelevant and
// always reports as enabled.
var flags []string
if with || ver.version >= VersionTLS13 {
flags = []string{expectEMSFlag}
}
testCases = append(testCases, testCase{
testType: testType,
name: prefix + "ExtendedMasterSecret-" + ver.name + suffix,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: !with,
RequireExtendedMasterSecret: with,
},
},
flags: flags,
})
}
}
}
for _, isClient := range []bool{false, true} {
for _, supportedInFirstConnection := range []bool{false, true} {
for _, supportedInResumeConnection := range []bool{false, true} {
boolToWord := func(b bool) string {
if b {
return "Yes"
}
return "No"
}
suffix := boolToWord(supportedInFirstConnection) + "To" + boolToWord(supportedInResumeConnection) + "-"
if isClient {
suffix += "Client"
} else {
suffix += "Server"
}
supportedConfig := Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
RequireExtendedMasterSecret: true,
},
}
noSupportConfig := Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: true,
},
}
test := testCase{
name: "ExtendedMasterSecret-" + suffix,
resumeSession: true,
}
if !isClient {
test.testType = serverTest
}
if supportedInFirstConnection {
test.config = supportedConfig
} else {
test.config = noSupportConfig
}
if supportedInResumeConnection {
test.resumeConfig = &supportedConfig
} else {
test.resumeConfig = &noSupportConfig
}
switch suffix {
case "YesToYes-Client", "YesToYes-Server":
// When a session is resumed, it should
// still be aware that its master
// secret was generated via EMS and
// thus it's safe to use tls-unique.
test.flags = []string{expectEMSFlag}
case "NoToYes-Server":
// If an original connection did not
// contain EMS, but a resumption
// handshake does, then a server should
// not resume the session.
test.expectResumeRejected = true
case "YesToNo-Server":
// Resuming an EMS session without the
// EMS extension should cause the
// server to abort the connection.
test.shouldFail = true
test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
case "NoToYes-Client":
// A client should abort a connection
// where the server resumed a non-EMS
// session but echoed the EMS
// extension.
test.shouldFail = true
test.expectedError = ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:"
case "YesToNo-Client":
// A client should abort a connection
// where the server didn't echo EMS
// when the session used it.
test.shouldFail = true
test.expectedError = ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:"
}
testCases = append(testCases, test)
}
}
}
// Switching EMS on renegotiation is forbidden.
testCases = append(testCases, testCase{
name: "ExtendedMasterSecret-Renego-NoEMS",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: true,
NoExtendedMasterSecretOnRenegotiation: true,
},
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
testCases = append(testCases, testCase{
name: "ExtendedMasterSecret-Renego-Upgrade",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: true,
},
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
shouldFail: true,
expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
})
testCases = append(testCases, testCase{
name: "ExtendedMasterSecret-Renego-Downgrade",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecretOnRenegotiation: true,
},
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
shouldFail: true,
expectedError: ":RENEGOTIATION_EMS_MISMATCH:",
})
}
+138
View File
@@ -0,0 +1,138 @@
// 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
// addEndOfFlightTests adds tests where the runner adds extra data in the final
// record of each handshake flight. Depending on the implementation strategy,
// this data may be carried over to the next flight (assuming no key change) or
// may be rejected. To avoid differences with split handshakes and generally
// reject misbehavior, BoringSSL treats this as an error. When possible, these
// tests pull the extra data from the subsequent flight to distinguish the data
// being carried over from a general syntax error.
//
// These tests are similar to tests in |addChangeCipherSpecTests| that send
// extra data at key changes. Not all key changes are at the end of a flight and
// not all flights end at a key change.
func addEndOfFlightTests() {
// TLS 1.3 client handshakes.
//
// Data following the second TLS 1.3 ClientHello is covered by
// PartialClientFinishedWithClientHello,
// PartialClientFinishedWithSecondClientHello, and
// PartialEndOfEarlyDataWithClientHello in |addChangeCipherSpecTests|.
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialSecondClientHelloAfterFirst",
config: Config{
MaxVersion: VersionTLS13,
// Trigger a curve-based HelloRetryRequest.
DefaultCurves: []CurveID{},
Bugs: ProtocolBugs{
PartialSecondClientHelloAfterFirst: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// TLS 1.3 server handshakes.
testCases = append(testCases, testCase{
testType: clientTest,
name: "PartialServerHelloWithHelloRetryRequest",
config: Config{
MaxVersion: VersionTLS13,
// P-384 requires HelloRetryRequest in BoringSSL.
CurvePreferences: []CurveID{CurveP384},
Bugs: ProtocolBugs{
PartialServerHelloWithHelloRetryRequest: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// TLS 1.2 client handshakes.
testCases = append(testCases, testCase{
testType: serverTest,
name: "PartialClientKeyExchangeWithClientHello",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
PartialClientKeyExchangeWithClientHello: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// TLS 1.2 server handshakes.
testCases = append(testCases, testCase{
testType: clientTest,
name: "PartialNewSessionTicketWithServerHelloDone",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
PartialNewSessionTicketWithServerHelloDone: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
for _, vers := range tlsVersions {
for _, testType := range []testType{clientTest, serverTest} {
suffix := "-Client"
if testType == serverTest {
suffix = "-Server"
}
suffix += "-" + vers.name
testCases = append(testCases, testCase{
testType: testType,
name: "TrailingDataWithFinished" + suffix,
config: Config{
MaxVersion: vers.version,
Bugs: ProtocolBugs{
TrailingDataWithFinished: true,
},
},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
testType: testType,
name: "TrailingDataWithFinished-Resume" + suffix,
config: Config{
MaxVersion: vers.version,
},
resumeConfig: &Config{
MaxVersion: vers.version,
Bugs: ProtocolBugs{
TrailingDataWithFinished: true,
},
},
resumeSession: true,
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
}
}
}
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addExportKeyingMaterialTests() {
for _, vers := range tlsVersions {
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-" + vers.name,
config: Config{
MaxVersion: vers.version,
},
// Test the exporter in both initial and resumption
// handshakes.
resumeSession: true,
exportKeyingMaterial: 1024,
exportLabel: "label",
exportContext: "context",
useExportContext: true,
})
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-NoContext-" + vers.name,
config: Config{
MaxVersion: vers.version,
},
exportKeyingMaterial: 1024,
})
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-EmptyContext-" + vers.name,
config: Config{
MaxVersion: vers.version,
},
exportKeyingMaterial: 1024,
useExportContext: true,
})
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-Small-" + vers.name,
config: Config{
MaxVersion: vers.version,
},
exportKeyingMaterial: 1,
exportLabel: "label",
exportContext: "context",
useExportContext: true,
})
if vers.version >= VersionTLS13 {
// Test the exporters do not work while the client is
// sending 0-RTT data.
testCases = append(testCases, testCase{
name: "NoEarlyKeyingMaterial-Client-InEarlyData-" + vers.name,
config: Config{
MaxVersion: vers.version,
},
resumeSession: true,
earlyData: true,
flags: []string{
"-on-resume-export-keying-material", "1024",
"-on-resume-export-label", "label",
"-on-resume-export-context", "context",
},
shouldFail: true,
expectedError: ":HANDSHAKE_NOT_COMPLETE:",
})
// Test the normal exporter on the server in half-RTT.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ExportKeyingMaterial-Server-HalfRTT-" + vers.name,
config: Config{
MaxVersion: vers.version,
Bugs: ProtocolBugs{
// The shim writes exported data immediately after
// the handshake returns, so disable the built-in
// early data test.
SendEarlyData: [][]byte{},
ExpectHalfRTTData: [][]byte{},
},
},
resumeSession: true,
earlyData: true,
exportKeyingMaterial: 1024,
exportLabel: "label",
exportContext: "context",
useExportContext: true,
})
}
}
// Exporters work during a False Start.
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-FalseStart",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
ExpectFalseStart: true,
},
},
flags: []string{
"-false-start",
"-advertise-alpn", "\x03foo",
"-expect-alpn", "foo",
},
shimWritesFirst: true,
exportKeyingMaterial: 1024,
exportLabel: "label",
exportContext: "context",
useExportContext: true,
})
// Exporters do not work in the middle of a renegotiation. Test this by
// triggering the exporter after every SSL_read call and configuring the
// shim to run asynchronously.
testCases = append(testCases, testCase{
name: "ExportKeyingMaterial-Renegotiate",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
flags: []string{
"-async",
"-use-exporter-between-reads",
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
shouldFail: true,
expectedError: "failed to export keying material",
})
}
func addExportTrafficSecretsTests() {
for _, cipherSuite := range []testCipherSuite{
// Test a SHA-256 and SHA-384 based cipher suite.
{"AEAD-AES128-GCM-SHA256", TLS_AES_128_GCM_SHA256},
{"AEAD-AES256-GCM-SHA384", TLS_AES_256_GCM_SHA384},
} {
testCases = append(testCases, testCase{
name: "ExportTrafficSecrets-" + cipherSuite.name,
config: Config{
MinVersion: VersionTLS13,
CipherSuites: []uint16{cipherSuite.id},
},
exportTrafficSecrets: true,
})
}
}
func addTLSUniqueTests() {
for _, isClient := range []bool{false, true} {
for _, isResumption := range []bool{false, true} {
for _, hasEMS := range []bool{false, true} {
var suffix string
if isResumption {
suffix = "Resume-"
} else {
suffix = "Full-"
}
if hasEMS {
suffix += "EMS-"
} else {
suffix += "NoEMS-"
}
if isClient {
suffix += "Client"
} else {
suffix += "Server"
}
test := testCase{
name: "TLSUnique-" + suffix,
testTLSUnique: true,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: !hasEMS,
},
},
}
if isResumption {
test.resumeSession = true
test.resumeConfig = &Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoExtendedMasterSecret: !hasEMS,
},
}
}
if isResumption && !hasEMS {
test.shouldFail = true
test.expectedError = "failed to get tls-unique"
}
testCases = append(testCases, test)
}
}
}
}
File diff suppressed because it is too large Load Diff
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addExtraHandshakeTests() {
// An extra SSL_do_handshake is normally a no-op. These tests use -async
// to ensure there is no transport I/O.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ExtraHandshake-Client-TLS12",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
"-async",
"-no-op-extra-handshake",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ExtraHandshake-Server-TLS12",
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
"-async",
"-no-op-extra-handshake",
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "ExtraHandshake-Client-TLS13",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-async",
"-no-op-extra-handshake",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ExtraHandshake-Server-TLS13",
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-async",
"-no-op-extra-handshake",
},
})
// An extra SSL_do_handshake is a no-op in server 0-RTT.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ExtraHandshake-Server-EarlyData-TLS13",
config: Config{
MaxVersion: VersionTLS13,
MinVersion: VersionTLS13,
},
messageCount: 2,
resumeSession: true,
earlyData: true,
flags: []string{
"-async",
"-no-op-extra-handshake",
},
})
// An extra SSL_do_handshake drives the handshake to completion in False
// Start. We test this by handshaking twice and asserting the False
// Start does not appear to happen. See AlertBeforeFalseStartTest for
// how the test works.
testCases = append(testCases, testCase{
testType: clientTest,
name: "ExtraHandshake-FalseStart",
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
Bugs: ProtocolBugs{
ExpectFalseStart: true,
AlertBeforeFalseStartTest: alertAccessDenied,
},
},
flags: []string{
"-handshake-twice",
"-false-start",
"-advertise-alpn", "\x03foo",
"-expect-alpn", "foo",
},
shimWritesFirst: true,
shouldFail: true,
expectedError: ":TLSV1_ALERT_ACCESS_DENIED:",
expectedLocalError: "tls: peer did not false start: EOF",
})
}
+491
View File
@@ -0,0 +1,491 @@
// 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 "strconv"
func addHintMismatchTests() {
// Each of these tests skips split handshakes because split handshakes does
// not handle a mismatch between shim and handshaker. Handshake hints,
// however, are designed to tolerate the mismatch.
//
// Note also these tests do not specify -handshake-hints directly. Instead,
// we define normal tests, that run even without a handshaker, and rely on
// convertToSplitHandshakeTests to generate a handshaker hints variant. This
// avoids repeating the -is-handshaker-supported and -handshaker-path logic.
// (While not useful, the tests will still pass without a handshaker.)
for _, protocol := range []protocol{tls, quic} {
// If the signing payload is different, the handshake still completes
// successfully. Different ALPN preferences will trigger a mismatch.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-SignatureInput",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
NextProtos: []string{"foo", "bar"},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-select-alpn", "foo",
"-on-handshaker-select-alpn", "bar",
},
expectations: connectionExpectations{
nextProto: "foo",
nextProtoType: alpn,
},
})
// The shim and handshaker may have different curve preferences.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-KeyShare",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
// Send both curves in the key share list, to avoid getting
// mixed up with HelloRetryRequest.
DefaultCurves: []CurveID{CurveX25519, CurveP256},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-ECDHE-Group",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
DefaultCurves: []CurveID{CurveX25519, CurveP256},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
}
// If the handshaker does HelloRetryRequest, it will omit most hints.
// The shim should still work.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-HandshakerHelloRetryRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveX25519},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-curves", strconv.Itoa(int(CurveX25519)),
"-on-handshaker-curves", strconv.Itoa(int(CurveP256)),
},
expectations: connectionExpectations{
curveID: CurveX25519,
},
})
// If the shim does HelloRetryRequest, the hints from the handshaker
// will be ignored. This is not reported as a mismatch because hints
// would not have helped the shim anyway.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-ShimHelloRetryRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveX25519},
},
flags: []string{
"-on-shim-curves", strconv.Itoa(int(CurveP256)),
"-on-handshaker-curves", strconv.Itoa(int(CurveX25519)),
},
expectations: connectionExpectations{
curveID: CurveP256,
},
})
// The shim and handshaker may have different signature algorithm
// preferences.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS13",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
VerifySignatureAlgorithms: []signatureAlgorithm{
signatureRSAPSSWithSHA256,
signatureRSAPSSWithSHA384,
},
},
shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
flags: []string{"-allow-hint-mismatch"},
expectations: connectionExpectations{
peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
},
})
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-SignatureAlgorithm-TLS12",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
VerifySignatureAlgorithms: []signatureAlgorithm{
signatureRSAPSSWithSHA256,
signatureRSAPSSWithSHA384,
},
},
shimCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
handshakerCertificate: rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
flags: []string{"-allow-hint-mismatch"},
expectations: connectionExpectations{
peerSignatureAlgorithm: signatureRSAPSSWithSHA256,
},
})
}
// The shim and handshaker may use different certificates. In TLS 1.3,
// the signature input includes the certificate, so we do not need to
// explicitly check for a public key match. In TLS 1.2, it does not.
ecdsaP256Certificate2 := generateSingleCertChain(nil, &channelIDKey)
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Certificate-TLS13",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
shimCertificate: &ecdsaP256Certificate,
handshakerCertificate: &ecdsaP256Certificate2,
flags: []string{"-allow-hint-mismatch"},
expectations: connectionExpectations{
peerCertificate: &ecdsaP256Certificate,
},
})
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Certificate-TLS12",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCertificate: &ecdsaP256Certificate,
handshakerCertificate: &ecdsaP256Certificate2,
flags: []string{"-allow-hint-mismatch"},
expectations: connectionExpectations{
peerCertificate: &ecdsaP256Certificate,
},
})
}
// The shim and handshaker may disagree on whether resumption is allowed.
// We run the first connection with tickets enabled, so the client is
// issued a ticket, then disable tickets on the second connection.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets1-TLS13",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-shim-on-resume-no-ticket",
},
resumeSession: true,
expectResumeRejected: true,
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets2-TLS13",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-handshaker-on-resume-no-ticket",
},
resumeSession: true,
})
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets1-TLS12",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-shim-on-resume-no-ticket",
},
resumeSession: true,
expectResumeRejected: true,
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-NoTickets2-TLS12",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
"-on-resume-allow-hint-mismatch",
"-on-handshaker-on-resume-no-ticket",
},
resumeSession: true,
})
}
// The shim and handshaker may disagree on whether to request a client
// certificate.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-CertificateRequest",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
Credential: &rsaCertificate,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-require-any-client-certificate",
},
})
// The shim and handshaker may negotiate different versions altogether.
if protocol != quic {
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Version1",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-max-version", strconv.Itoa(VersionTLS12),
"-on-handshaker-max-version", strconv.Itoa(VersionTLS13),
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-Version2",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS13,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-max-version", strconv.Itoa(VersionTLS13),
"-on-handshaker-max-version", strconv.Itoa(VersionTLS12),
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
}
// The shim and handshaker may disagree on the certificate compression
// algorithm, whether to enable certificate compression, or certificate
// compression inputs.
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-CertificateCompression-ShimOnly",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-install-cert-compression-algs",
},
})
testCases = append(testCases, testCase{
name: protocol.String() + "-HintMismatch-CertificateCompression-HandshakerOnly",
testType: serverTest,
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectUncompressedCert: true,
},
},
flags: []string{
"-allow-hint-mismatch",
"-on-handshaker-install-cert-compression-algs",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: protocol.String() + "-HintMismatch-CertificateCompression-AlgorithmMismatch",
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
expandingCompressionAlgID: expandingCompression,
},
Bugs: ProtocolBugs{
// The shim's preferences should take effect.
ExpectedCompressedCert: shrinkingCompressionAlgID,
},
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID),
"-on-handshaker-install-one-cert-compression-alg", strconv.Itoa(expandingCompressionAlgID),
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: protocol.String() + "-HintMismatch-CertificateCompression-InputMismatch",
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS13,
MaxVersion: VersionTLS13,
CertCompressionAlgs: map[uint16]CertCompressionAlg{
shrinkingCompressionAlgID: shrinkingCompression,
},
Bugs: ProtocolBugs{
ExpectedCompressedCert: shrinkingCompressionAlgID,
},
},
// Configure the shim and handshaker with different OCSP responses,
// so the compression inputs do not match.
shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
handshakerCertificate: rsaCertificate.WithOCSP(testOCSPResponse2),
flags: []string{
"-allow-hint-mismatch",
"-install-cert-compression-algs",
},
expectations: connectionExpectations{
// The shim's configuration should take precendence.
peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
},
})
// The shim and handshaker may disagree on cipher suite, to the point
// that one selects RSA key exchange (no applicable hint) and the other
// selects ECDHE_RSA (hints are useful).
if protocol != quic {
testCases = append(testCases, testCase{
testType: serverTest,
name: protocol.String() + "-HintMismatch-CipherMismatch1",
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
"-allow-hint-mismatch",
"-on-shim-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"-on-handshaker-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
},
expectations: connectionExpectations{
cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: protocol.String() + "-HintMismatch-CipherMismatch2",
protocol: protocol,
skipSplitHandshake: true,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
flags: []string{
// There is no need to pass -allow-hint-mismatch. The
// handshaker will unnecessarily generate a signature hints.
// This is not reported as a mismatch because hints would
// not have helped the shim anyway.
"-on-shim-cipher", "TLS_RSA_WITH_AES_128_GCM_SHA256",
"-on-handshaker-cipher", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
},
expectations: connectionExpectations{
cipher: TLS_RSA_WITH_AES_128_GCM_SHA256,
},
})
}
}
}
+182
View File
@@ -0,0 +1,182 @@
// 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"
"strconv"
)
func addJDK11WorkaroundTests() {
// Test the client treats the JDK 11 downgrade random like the usual one.
testCases = append(testCases, testCase{
testType: clientTest,
name: "Client-RejectJDK11DowngradeRandom",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendJDK11DowngradeRandom: true,
},
},
shouldFail: true,
expectedError: ":TLS13_DOWNGRADE:",
expectedLocalError: "remote error: illegal parameter",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "Client-AcceptJDK11DowngradeRandom",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendJDK11DowngradeRandom: true,
},
},
flags: []string{"-max-version", strconv.Itoa(VersionTLS12)},
})
clientHelloTests := []struct {
clientHello []byte
isJDK11 bool
}{
{
// A default JDK 11 ClientHello.
decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
true,
},
{
// The above with supported_versions and
// psk_key_exchange_modes in the wrong order.
decodeHexOrPanic("010001a9030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002d00020101002b00090803040303030203010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b5"),
false,
},
{
// The above with a padding extension added at the end.
decodeHexOrPanic("010001b4030336a379aa355a22a064b4402760efae1c73977b0b4c975efc7654c35677723dde201fe3f8a2bca60418a68f72463ea19f3c241e7cbfceb347e451a62bd2417d8981005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000111000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104721f007464cb08a0f36e093ad178eb78d6968df20077b2dd882694a85dc4c9884caf5092db41f16cc3f8d41f59426992fa5e32cfb9ad08deee752cdd95b1a6b50015000700000000000000"),
false,
},
{
// A JDK 11 ClientHello offering a TLS 1.3 PSK.
decodeHexOrPanic("0100024c0303a8d71b20f060545a398226e807d21371a7a02b7ca2f96f476c2dea7e5860c5a400005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010001c9000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d000201010033004700450017004104aaec585ea9e121b24710a23560571322b2cf8ab8cd14e5762ef0486d8a6d0ecd721d8f2abda2eb8ed5ab7195505660450f49bba94bbf0c3f0070a531d9a1be4f002900cb00a600a0e6f7586d9a2bf64a54c1adf55a2f76657047e8e88e26629e2e7b9d630941e06fd87792770f6834e159a70b252157a9b4b082183f24629c8ff5049088b07ce37c49de8cf752a2ed7a545aff63bdc7a1b18e1bc201f23f159ee75d4987a04e00f840824f764691ab83a20e3032646e793065874cdb46138a52f50ed71406f399f96f9309eba4e5b1966148c22a63dc4aa1364269dd41dd5cc0e848d07af0095622c52cfcfc00212009cc315259e2328d65ad17a3de7c182c7874140a9356fecdd4614657806cd659"),
true,
},
{
// A JDK 11 ClientHello offering a TLS 1.2 session.
decodeHexOrPanic("010001a903038cdec49f4836d064a75046c93f22d0b9c2cf4900917332e6f0e1f41d692d3146201a3e99047492285ec65ab4e0eeee59f8f9d1eb7687398887bcd7b81353e93923005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000106000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b0009080304030303020301002d0002010100330047004500170041041c83c42fcd8fc06265b9f6e4f076f7e7ee17ace915c587845c0e1bc8cd177f904befeb611b682cae4702509a5f5d0c7162a282b8152d843169b91136e7c6f3e7"),
true,
},
{
// A JDK 11 ClientHello with EMS disabled.
decodeHexOrPanic("010001a50303323a857c324a9ef57d6e2544d129073830385cb1dc75ea79f6a2ec8ae09d2e7320f85fdd081678874c67ebab235e6d6a81d947f690bc0af9be4d39854ed67d9ef9005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000102000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200110009000702000400000000002b0009080304030303020301002d0002010100330047004500170041049c904c4850b495d75522f955d79e9cabea065c90279d6037a101a4c4ee712afc93ad0df5d12d287d53e458c7075d9a3ce3969c939bb62222bda779cecf54a603"),
true,
},
{
// A JDK 11 ClientHello with OCSP stapling disabled.
decodeHexOrPanic("0100019303038a50481dc85ee4f6581670821c50f2b3d34ac3251dc6e9b751bfd2521ab47ab02069a963c5486034c37ae0577ddb4c2db28cab592380ef8e4599d1305148712112005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff010000f0000000080006000003736e69000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b040105010601040203030301030202030201020200170000002b0009080304030303020301002d00020101003300470045001700410438a97824f842c549e3c339322d8b2dbaa85d10bd7bca9c969376cb0c60b1e929eb4d13db38dcb0082ad8c637b24f55466a9acbb0b63634c1f431ec8342cf720d"),
true,
},
{
// A JDK 11 ClientHello configured with a smaller set of
// ciphers.
decodeHexOrPanic("0100015603036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
true,
},
{
// The above with TLS_CHACHA20_POLY1305_SHA256 added,
// which JDK 11 does not support.
decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f48118000813011303c02bc02f01000107000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
false,
},
{
// The above with X25519 added, which JDK 11 does not
// support.
decodeHexOrPanic("0100015803036f5706bbdf1dcae671cd9be043603f5ed20f8fc195b426504cafb4f353edb0012007aabd35e588bc2504a72eda42cbbf89d69cfc0a6a1d77db0d757606f1f4811800061301c02bc02f01000109000000080006000003736e69000500050100000000000a00220020001d0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020011000900070200040000000000170000002b00050403040303002d000201010033004700450017004104d283f3d5a90259b61d43ea1511211f568ce5d18457326b717e1f9d6b7d1476f2b51cdc3c798d3bdfba5095edff0ffd0540f6bc0c324bd9744f3b3f24317496e3ff01000100"),
false,
},
{
// A JDK 11 ClientHello with ALPN protocols configured.
decodeHexOrPanic("010001bb0303c0e0ea707b00c5311eb09cabd58626692cebfaefaef7265637e4550811dae16220da86d6eea04e214e873675223f08a6926bcf79f16d866280bdbab85e9e09c3ff005a13011302c02cc02bc030009dc02ec032009f00a3c02f009cc02dc031009e00a2c024c028003dc026c02a006b006ac00ac0140035c005c00f00390038c023c027003cc025c02900670040c009c013002fc004c00e0033003200ff01000118000000080006000003736e69000500050100000000000a0020001e0017001800190009000a000b000c000d000e001601000101010201030104000b00020100000d002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020032002800260403050306030804080508060809080a080b04010501060104020303030103020203020102020010000e000c02683208687474702f312e310011000900070200040000000000170000002b0009080304030303020301002d00020101003300470045001700410416def07c1d66ddde5fc9dcc328c8e77022d321c590c0d30cb41d515b38dca34540819a216c6c053bd47b9068f4f6b960f03647de4e36e8b7ffeea78f7252e3d9"),
true,
},
}
for i, t := range clientHelloTests {
expectedVersion := uint16(VersionTLS13)
if t.isJDK11 {
expectedVersion = VersionTLS12
}
// In each of these tests, we set DefaultCurves to P-256 to
// match the test inputs. SendClientHelloWithFixes requires the
// key_shares extension to match in type.
// With the workaround enabled, we should negotiate TLS 1.2 on
// JDK 11 ClientHellos.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("Server-JDK11-%d", i),
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveP256},
Bugs: ProtocolBugs{
SendClientHelloWithFixes: t.clientHello,
ExpectJDK11DowngradeRandom: t.isJDK11,
},
},
expectations: connectionExpectations{
version: expectedVersion,
},
flags: []string{"-jdk11-workaround"},
})
// With the workaround disabled, we always negotiate TLS 1.3.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("Server-JDK11-NoWorkaround-%d", i),
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveP256},
Bugs: ProtocolBugs{
SendClientHelloWithFixes: t.clientHello,
ExpectJDK11DowngradeRandom: false,
},
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
// If the server does not support TLS 1.3, the workaround should
// be a no-op. In particular, it should not send the downgrade
// signal.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("Server-JDK11-TLS12-%d", i),
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{CurveP256},
Bugs: ProtocolBugs{
SendClientHelloWithFixes: t.clientHello,
ExpectJDK11DowngradeRandom: false,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
flags: []string{
"-jdk11-workaround",
"-max-version", strconv.Itoa(VersionTLS12),
},
})
}
}
+597
View File
@@ -0,0 +1,597 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
import "slices"
func addKeyUpdateTests() {
// TLS tests.
testCases = append(testCases, testCase{
name: "KeyUpdate-ToClient",
config: Config{
MaxVersion: VersionTLS13,
},
sendKeyUpdates: 10,
keyUpdateRequest: keyUpdateNotRequested,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "KeyUpdate-ToServer",
config: Config{
MaxVersion: VersionTLS13,
},
sendKeyUpdates: 10,
keyUpdateRequest: keyUpdateNotRequested,
})
testCases = append(testCases, testCase{
name: "KeyUpdate-FromClient",
config: Config{
MaxVersion: VersionTLS13,
},
expectUnsolicitedKeyUpdate: true,
flags: []string{"-key-update"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "KeyUpdate-FromServer",
config: Config{
MaxVersion: VersionTLS13,
},
expectUnsolicitedKeyUpdate: true,
flags: []string{"-key-update"},
})
testCases = append(testCases, testCase{
name: "KeyUpdate-InvalidRequestMode",
config: Config{
MaxVersion: VersionTLS13,
},
sendKeyUpdates: 1,
keyUpdateRequest: 42,
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
// Test that shim responds to KeyUpdate requests.
name: "KeyUpdate-Requested",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
RejectUnsolicitedKeyUpdate: true,
},
},
// Test the shim receiving many KeyUpdates in a row.
sendKeyUpdates: 5,
messageCount: 5,
keyUpdateRequest: keyUpdateRequested,
})
testCases = append(testCases, testCase{
// Test that shim responds to KeyUpdate requests if peer's KeyUpdate is
// discovered while a write is pending.
name: "KeyUpdate-Requested-UnfinishedWrite",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
RejectUnsolicitedKeyUpdate: true,
},
},
// Test the shim receiving many KeyUpdates in a row.
sendKeyUpdates: 5,
messageCount: 5,
keyUpdateRequest: keyUpdateRequested,
readWithUnfinishedWrite: true,
flags: []string{"-async"},
})
// DTLS tests.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ToClient-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
// Send many KeyUpdates to make sure record reassembly can handle it.
sendKeyUpdates: 10,
keyUpdateRequest: keyUpdateNotRequested,
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "KeyUpdate-ToServer-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
sendKeyUpdates: 10,
keyUpdateRequest: keyUpdateNotRequested,
})
// Test that the shim accounts for packet loss when processing KeyUpdate.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ToClient-PacketLoss-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
if next[0].Type != typeKeyUpdate {
c.WriteFlight(next)
return
}
// Send the KeyUpdate. The shim should ACK it.
c.WriteFlight(next)
ackTimeout := timeouts[0] / 4
c.AdvanceClock(ackTimeout)
c.ReadACK(c.InEpoch())
// The shim should continue reading data at the old epoch.
// The ACK may not have come through.
msg := []byte("test")
c.WriteAppData(c.OutEpoch()-1, msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
// Re-send KeyUpdate. The shim should ACK it again. The ACK
// may not have come through.
c.WriteFlight(next)
c.AdvanceClock(ackTimeout)
c.ReadACK(c.InEpoch())
// The shim should be able to read data at the new epoch.
c.WriteAppData(c.OutEpoch(), msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
// The shim continues to accept application data at the old
// epoch, for a period of time.
c.WriteAppData(c.OutEpoch()-1, msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
// It will even ACK the retransmission, though it knows the
// shim has seen the ACK.
c.WriteFlight(next)
c.AdvanceClock(ackTimeout)
c.ReadACK(c.InEpoch())
// After some time has passed, the shim will discard the old
// epoch. The following writes should be ignored.
c.AdvanceClock(dtlsPrevEpochExpiration)
f := next[0].Fragment(0, len(next[0].Data))
f.ShouldDiscard = true
c.WriteFragments([]DTLSFragment{f})
c.WriteAppData(c.OutEpoch()-1, msg)
},
},
},
sendKeyUpdates: 10,
keyUpdateRequest: keyUpdateNotRequested,
flags: []string{"-async"},
})
// In DTLS, we KeyUpdate before read, rather than write, because the
// KeyUpdate will not be applied before the shim reads the ACK.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-FromClient-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
shimSendsKeyUpdateBeforeRead: true,
// Perform several message exchanges to update keys several times.
messageCount: 10,
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "KeyUpdate-FromServer-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
shimSendsKeyUpdateBeforeRead: true,
// Perform several message exchanges to update keys several times.
messageCount: 10,
// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
flags: []string{"-no-ticket"},
})
// If the shim has a pending unACKed flight, it defers sending KeyUpdate.
// BoringSSL does not support multiple outgoing flights at once.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-DeferredSend-DTLS",
config: Config{
MaxVersion: VersionTLS13,
// Request a client certificate, so the shim has more to send.
ClientAuth: RequireAnyClientCert,
Bugs: ProtocolBugs{
MaxPacketLength: 512,
ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
if received[len(received)-1].Type != typeFinished {
c.WriteACK(c.OutEpoch(), records)
return
}
// This test relies on the Finished flight being multiple
// records.
if len(records) <= 1 {
panic("shim sent Finished flight in one record")
}
// Before ACKing Finished, do some rounds of exchanging
// application data. Although the shim has already scheduled
// KeyUpdate, it should not send the KeyUpdate until it gets
// an ACK. (If it sent KeyUpdate, ReadAppData would report
// an unexpected record.)
msg := []byte("test")
for i := 0; i < 10; i++ {
c.WriteAppData(c.OutEpoch(), msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
}
// ACK some of the Finished flight, but not all of it.
c.WriteACK(c.OutEpoch(), records[:1])
// The shim continues to defer KeyUpdate.
for i := 0; i < 10; i++ {
c.WriteAppData(c.OutEpoch(), msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
}
// ACK the remainder.
c.WriteACK(c.OutEpoch(), records[1:])
// The shim should now send KeyUpdate. Return to the test
// harness, which will look for it.
},
},
},
shimCertificate: &rsaChainCertificate,
shimSendsKeyUpdateBeforeRead: true,
flags: []string{"-mtu", "512"},
})
// The shim should not switch keys until it receives an ACK.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-WaitForACK-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
MaxPacketLength: 512,
ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
if received[0].Type != typeKeyUpdate {
c.WriteACK(c.OutEpoch(), records)
return
}
// Make the shim send application data. We have not yet
// ACKed KeyUpdate, so the shim should send at the previous
// epoch. Through each of these rounds, the shim will also
// try to KeyUpdate again. These calls will be suppressed
// because there is still an outstanding KeyUpdate.
msg := []byte("test")
for i := 0; i < 10; i++ {
c.WriteAppData(c.OutEpoch(), msg)
c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
}
// ACK the KeyUpdate. Ideally we'd test a partial ACK, but
// BoringSSL's minimum MTU is such that KeyUpdate always
// fits in one record.
c.WriteACK(c.OutEpoch(), records)
// The shim should now send at the new epoch. Return to the
// test harness, which will enforce this.
},
},
},
shimSendsKeyUpdateBeforeRead: true,
})
// Test that shim responds to KeyUpdate requests.
fixKeyUpdateReply := func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
c.WriteACK(c.OutEpoch(), records)
if received[0].Type != typeKeyUpdate {
return
}
// This works around an awkward testing mismatch. The test
// harness expects the shim to immediately change keys, but
// the shim writes app data before seeing the ACK. The app
// data will be sent at the previous epoch. Consume this and
// prime the shim to resend its reply at the new epoch.
msg := makeTestMessage(int(received[0].Sequence)-2, 32)
c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
c.WriteAppData(c.OutEpoch(), msg)
}
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-Requested-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
RejectUnsolicitedKeyUpdate: true,
ACKFlightDTLS: fixKeyUpdateReply,
},
},
// Test the shim receiving many KeyUpdates in a row. They will be
// combined into one reply KeyUpdate.
sendKeyUpdates: 5,
messageLen: 32,
messageCount: 5,
keyUpdateRequest: keyUpdateRequested,
})
mergeNewSessionTicketAndKeyUpdate := func(f WriteFlightFunc) WriteFlightFunc {
return func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
// Send NewSessionTicket and the first KeyUpdate all together.
if next[0].Type == typeKeyUpdate {
panic("key update should have been merged into NewSessionTicket")
}
if next[0].Type != typeNewSessionTicket {
c.WriteFlight(next)
return
}
if next[0].Type == typeNewSessionTicket && next[len(next)-1].Type != typeKeyUpdate {
c.MergeIntoNextFlight()
return
}
f(c, prev, received, next, records)
}
}
// Test that the shim does not process KeyUpdate until it has processed all
// preceding messages.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ProcessInOrder-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
// Write the KeyUpdate. The shim should buffer and ACK it.
keyUpdate := next[len(next)-1]
c.WriteFlight([]DTLSMessage{keyUpdate})
ackTimeout := timeouts[0] / 4
c.AdvanceClock(ackTimeout)
c.ReadACK(c.InEpoch())
// The shim should not process KeyUpdate yet. It should not
// read from the new epoch.
msg1, msg2 := []byte("aaaa"), []byte("bbbb")
c.WriteAppData(c.OutEpoch(), msg1)
c.AdvanceClock(0) // Check there are no messages.
// It can read from the old epoch, however.
c.WriteAppData(c.OutEpoch()-1, msg2)
c.ReadAppData(c.InEpoch(), expectedReply(msg2))
// Write the rest of the flight.
c.WriteFlight(next[:len(next)-1])
c.AdvanceClock(ackTimeout)
c.ReadACK(c.InEpoch())
// Now the new epoch is functional.
c.WriteAppData(c.OutEpoch(), msg1)
c.ReadAppData(c.InEpoch(), expectedReply(msg1))
}),
},
},
sendKeyUpdates: 1,
keyUpdateRequest: keyUpdateNotRequested,
flags: []string{"-async"},
})
// Messages after a KeyUpdate are not allowed.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ExtraMessage-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
extra := next[0]
extra.Sequence = next[len(next)-1].Sequence + 1
next = append(slices.Clip(next), extra)
c.WriteFlight(next)
}),
},
},
sendKeyUpdates: 1,
keyUpdateRequest: keyUpdateNotRequested,
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ExtraMessageBuffered-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
WriteFlightDTLS: mergeNewSessionTicketAndKeyUpdate(func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
// Send the extra message first. The shim should accept and
// buffer it.
extra := next[0]
extra.Sequence = next[len(next)-1].Sequence + 1
c.WriteFlight([]DTLSMessage{extra})
// Now send the flight, including a KeyUpdate. The shim
// should now notice the extra message and reject.
c.WriteFlight(next)
}),
},
},
sendKeyUpdates: 1,
keyUpdateRequest: keyUpdateNotRequested,
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// Test KeyUpdate overflow conditions. Both the epoch number and the message
// number may overflow, in either the read or write direction.
// When the sender is the client, the first KeyUpdate is message 2 at epoch
// 3, so the epoch number overflows first.
const maxClientKeyUpdates = 0xffff - 3
// Test that the shim, as a server, rejects KeyUpdates at epoch 0xffff. RFC
// 9147 does not prescribe this limit, but we enforce it. See
// https://mailarchive.ietf.org/arch/msg/tls/6y8wTv8Q_IPM-PCcbCAmDOYg6bM/
// and https://www.rfc-editor.org/errata/eid8050
writeFlightKeyUpdate := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
if next[0].Type == typeKeyUpdate {
// Exchange some data to avoid tripping KeyUpdate DoS limits.
msg := []byte("test")
c.WriteAppData(c.OutEpoch()-1, msg)
c.ReadAppData(c.InEpoch(), expectedReply(msg))
}
c.WriteFlight(next)
}
testCases = append(testCases, testCase{
testType: serverTest,
protocol: dtls,
name: "KeyUpdate-MaxReadEpoch-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
AllowEpochOverflow: true,
WriteFlightDTLS: writeFlightKeyUpdate,
},
},
// Avoid the NewSessionTicket messages interfering with the callback.
flags: []string{"-no-ticket"},
sendKeyUpdates: maxClientKeyUpdates,
keyUpdateRequest: keyUpdateNotRequested,
})
testCases = append(testCases, testCase{
testType: serverTest,
protocol: dtls,
name: "KeyUpdate-ReadEpochOverflow-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
AllowEpochOverflow: true,
WriteFlightDTLS: writeFlightKeyUpdate,
},
},
// Avoid the NewSessionTicket messages interfering with the callback.
flags: []string{"-no-ticket"},
sendKeyUpdates: maxClientKeyUpdates + 1,
keyUpdateRequest: keyUpdateNotRequested,
shouldFail: true,
expectedError: ":TOO_MANY_KEY_UPDATES:",
expectedLocalError: "remote error: unexpected message",
})
// Test that the shim, as a client, notices its epoch overflow condition
// when asked to send too many KeyUpdates. The shim sends KeyUpdate before
// every read, including reading connection close, so the number of
// KeyUpdates is one more than the message count.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-MaxWriteEpoch-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
shimSendsKeyUpdateBeforeRead: true,
messageCount: maxClientKeyUpdates - 1,
})
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-WriteEpochOverflow-DTLS",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
// The shim does not notice the overflow until immediately after
// sending KeyUpdate, so tolerate the overflow on the runner.
AllowEpochOverflow: true,
},
},
shimSendsKeyUpdateBeforeRead: true,
messageCount: maxClientKeyUpdates,
shouldFail: true,
expectedError: ":TOO_MANY_KEY_UPDATES:",
})
// When the sender is a server that doesn't send tickets, the first
// KeyUpdate is message 5 (SH, EE, C, CV, Fin) at epoch 3, so the message
// number overflows first.
const maxServerKeyUpdates = 0xffff - 5
// Test that the shim, as a client, does not allow the value to wraparound.
testCases = append(testCases, testCase{
protocol: dtls,
name: "KeyUpdate-ReadMessageOverflow-DTLS",
config: Config{
MaxVersion: VersionTLS13,
SessionTicketsDisabled: true,
Bugs: ProtocolBugs{
AllowEpochOverflow: true,
WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
writeFlightKeyUpdate(c, prev, received, next, records)
if next[0].Type == typeKeyUpdate && next[0].Sequence == 0xffff {
// At this point, the shim has accepted message 0xffff.
// Check the shim does not now accept message 0 as the
// current message. Test this by sending a garbage
// message 0. A shim that overflows and processes the
// message will notice the syntax error. A shim that
// correctly interprets this as an old message will drop
// the record and simply ACK it.
//
// We do this rather than send a valid KeyUpdate because
// the shim will keep the old epoch active and drop
// decryption failures. Looking for the lack of an error
// is more straightforward.
c.WriteFlight([]DTLSMessage{{Epoch: c.OutEpoch(), Sequence: 0, Type: typeKeyUpdate, Data: []byte("INVALID")}})
c.ExpectNextTimeout(timeouts[0] / 4)
c.AdvanceClock(timeouts[0] / 4)
c.ReadACK(c.InEpoch())
}
},
},
},
sendKeyUpdates: maxServerKeyUpdates + 1,
keyUpdateRequest: keyUpdateNotRequested,
flags: []string{"-async", "-expect-no-session"},
})
// Test that the shim, as a server, notices its message overflow condition,
// when asked to send too many KeyUpdates.
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "KeyUpdate-MaxWriteMessage-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
shimSendsKeyUpdateBeforeRead: true,
messageCount: maxServerKeyUpdates,
// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
flags: []string{"-no-ticket"},
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "KeyUpdate-WriteMessageOverflow-DTLS",
config: Config{
MaxVersion: VersionTLS13,
},
shimSendsKeyUpdateBeforeRead: true,
messageCount: maxServerKeyUpdates + 1,
shouldFail: true,
expectedError: ":overflow:",
// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
flags: []string{"-no-ticket"},
})
}
+249
View File
@@ -0,0 +1,249 @@
// 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/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"math/big"
"time"
)
func addECDSAKeyUsageTests() {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
panic(err)
}
template := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now(),
// An ECC certificate with only the keyAgreement key usgae may
// be used with ECDH, but not ECDSA.
KeyUsage: x509.KeyUsageKeyAgreement,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
cert := generateSingleCertChain(template, &ecdsaP256Key)
for _, ver := range tlsVersions {
if ver.version < VersionTLS12 {
continue
}
testCases = append(testCases, testCase{
testType: clientTest,
name: "ECDSAKeyUsage-Client-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &cert,
},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ECDSAKeyUsage-Server-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &cert,
},
flags: []string{"-require-any-client-certificate"},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
})
}
}
func addRSAKeyUsageTests() {
priv := rsaCertificate.PrivateKey.(*rsa.PrivateKey)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
panic(err)
}
dsTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now(),
KeyUsage: x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
}
encTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: time.Now(),
NotAfter: time.Now(),
KeyUsage: x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
}
dsCert := generateSingleCertChain(&dsTemplate, priv)
encCert := generateSingleCertChain(&encTemplate, priv)
dsSuites := []uint16{
TLS_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
}
encSuites := []uint16{
TLS_RSA_WITH_AES_128_GCM_SHA256,
TLS_RSA_WITH_AES_128_CBC_SHA,
}
for _, ver := range tlsVersions {
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &encCert,
CipherSuites: dsSuites,
},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantSignature-GotSignature-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &dsCert,
CipherSuites: dsSuites,
},
})
// TLS 1.3 removes the encipherment suites.
if ver.version < VersionTLS13 {
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantEncipherment-GotEncipherment" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &encCert,
CipherSuites: encSuites,
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &dsCert,
CipherSuites: encSuites,
},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
})
// In 1.2 and below, we should not enforce without the enforce-rsa-key-usage flag.
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-Unenforced-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &dsCert,
CipherSuites: encSuites,
},
flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantEncipherment-GotSignature-Unenforced-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &encCert,
CipherSuites: dsSuites,
},
flags: []string{"-expect-key-usage-invalid", "-ignore-rsa-key-usage"},
})
}
if ver.version >= VersionTLS13 {
// In 1.3 and above, we enforce keyUsage even when disabled.
testCases = append(testCases, testCase{
testType: clientTest,
name: "RSAKeyUsage-Client-WantSignature-GotEncipherment-AlwaysEnforced-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &encCert,
CipherSuites: dsSuites,
},
flags: []string{"-ignore-rsa-key-usage"},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
})
}
// The server only uses signatures and always enforces it.
testCases = append(testCases, testCase{
testType: serverTest,
name: "RSAKeyUsage-Server-WantSignature-GotEncipherment-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &encCert,
},
shouldFail: true,
expectedError: ":KEY_USAGE_BIT_INCORRECT:",
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "RSAKeyUsage-Server-WantSignature-GotSignature-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &dsCert,
},
flags: []string{"-require-any-client-certificate"},
})
}
}
+513
View File
@@ -0,0 +1,513 @@
// 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 "errors"
func addPAKETests() {
spakeCredential := Credential{
Type: CredentialTypeSPAKE2PlusV1,
PAKEContext: []byte("context"),
PAKEClientID: []byte("client"),
PAKEServerID: []byte("server"),
PAKEPassword: []byte("password"),
}
spakeWrongClientID := spakeCredential
spakeWrongClientID.PAKEClientID = []byte("wrong")
spakeWrongServerID := spakeCredential
spakeWrongServerID.PAKEServerID = []byte("wrong")
spakeWrongPassword := spakeCredential
spakeWrongPassword.PAKEPassword = []byte("wrong")
spakeWrongRole := spakeCredential
spakeWrongRole.WrongPAKERole = true
spakeWrongCodepoint := spakeCredential
spakeWrongCodepoint.OverridePAKECodepoint = 1234
testCases = append(testCases, testCase{
name: "PAKE-No-Server-Support",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shouldFail: true,
expectedError: ":MISSING_KEY_SHARE:",
})
testCases = append(testCases, testCase{
name: "PAKE-Server",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
// We do not currently support resumption with PAKE, so PAKE
// servers should not issue session tickets.
ExpectNoNewSessionTicket: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE client ID.
name: "PAKE-Server-WrongClientID",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongClientID,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE server ID.
name: "PAKE-Server-WrongServerID",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongServerID,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// Send a ClientHello with the wrong PAKE codepoint.
name: "PAKE-Server-WrongCodepoint",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongCodepoint,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// A server configured with a mix of PAKE and non-PAKE
// credentials will select the first that matches what the
// client offered. In doing so, it should skip unsupported
// PAKE algorithms.
name: "PAKE-Server-MultiplePAKEs",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
OfferExtraPAKEs: []uint16{1, 2, 3, 4, 5},
},
},
shimCredentials: []*Credential{&spakeWrongClientID, &spakeWrongServerID, &spakeWrongRole, &spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "3"},
})
testCases = append(testCases, testCase{
// A server configured with a certificate credential before a
// PAKE credential will consider the certificate credential first.
name: "PAKE-Server-CertificateBeforePAKE",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Pretend to offer a matching PAKE share, but expect the
// shim to select the credential first and negotiate a
// normal handshake.
OfferExtraPAKEClientID: spakeCredential.PAKEClientID,
OfferExtraPAKEServerID: spakeCredential.PAKEServerID,
OfferExtraPAKEs: []uint16{spakeID},
},
},
shimCredentials: []*Credential{&rsaCertificate, &spakeCredential},
flags: []string{"-expect-selected-credential", "0"},
})
testCases = append(testCases, testCase{
// A server configured with just a PAKE credential should reject normal
// clients.
name: "PAKE-Server-NormalClient",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":PEER_PAKE_MISMATCH:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// ... and TLS 1.2 clients.
name: "PAKE-Server-NormalTLS12Client",
testType: serverTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":NO_SHARED_CIPHER:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// ... but you can configure a server with both PAKE and certificate-based
// SSL_CREDENTIALs and that works.
name: "PAKE-ServerWithCertsToo-NormalClient",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
// ... and for older clients.
name: "PAKE-ServerWithCertsToo-NormalTLS12Client",
testType: serverTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
name: "PAKE-Client",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
CheckClientHello: func(c *clientHelloMsg) error {
// PAKE connections don't use the key_share / supported_groups mechanism.
if c.hasKeyShares {
return errors.New("unexpected key_share extension")
}
if len(c.supportedCurves) != 0 {
return errors.New("unexpected supported_groups extension")
}
// PAKE connections don't use signature algorithms.
if len(c.signatureAlgorithms) != 0 {
return errors.New("unexpected signature_algorithms extension")
}
// We don't support resumption with PAKEs.
if len(c.pskKEModes) != 0 {
return errors.New("unexpected psk_key_exchange_modes extension")
}
return nil
},
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// Although there is no reason to request new key shares, the PAKE
// client should handle cookie requests.
name: "PAKE-Client-HRRCookie",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
SendHelloRetryRequestCookie: []byte("cookie"),
},
},
shimCredentials: []*Credential{&spakeCredential},
})
testCases = append(testCases, testCase{
// A PAKE client will not offer key shares, so the client should
// reject a HelloRetryRequest requesting a different key share.
name: "PAKE-Client-HRRKeyShare",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
SendHelloRetryRequestCurve: CurveX25519,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
testCases = append(testCases, testCase{
// A server cannot reply with an HRR asking for a PAKE if the client didn't
// offer a PAKE in the ClientHello.
name: "PAKE-NormalClient-PAKEInHRR",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
AlwaysSendHelloRetryRequest: true,
SendPAKEInHelloRetryRequest: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept an empty ServerHello.
name: "PAKE-Client-EmptyServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Trigger an empty ServerHello by making a normal server skip
// the key_share extension.
MissingKeyShare: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":MISSING_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept a key_share ServerHello.
name: "PAKE-Client-KeyShareServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
// Trigger a key_share ServerHello by making a normal server
// skip the HelloRetryRequest it would otherwise send in
// response to the shim's key_share-less ClientHello.
SkipHelloRetryRequest: true,
// Ignore the client's lack of supported_groups.
IgnorePeerCurvePreferences: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A PAKE client should not accept a TLS 1.2 ServerHello.
name: "PAKE-Client-TLS12ServerHello",
testType: clientTest,
config: Config{
MinVersion: VersionTLS12,
MaxVersion: VersionTLS12,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
// A server cannot send the PAKE extension to a non-PAKE client.
name: "PAKE-NormalClient-UnsolicitedPAKEInServerHello",
testType: clientTest,
config: Config{
Bugs: ProtocolBugs{
UnsolicitedPAKE: spakeID,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
// A server cannot reply with a PAKE that the client did not offer.
name: "PAKE-Client-WrongPAKEInServerHello",
testType: clientTest,
config: Config{
Bugs: ProtocolBugs{
UnsolicitedPAKE: 1234,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Extension-Duplicate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
OfferExtraPAKEClientID: []byte("client"),
OfferExtraPAKEServerID: []byte("server"),
OfferExtraPAKEs: []uint16{1234, 1234},
},
},
shouldFail: true,
expectedError: ":ERROR_PARSING_EXTENSION:",
})
testCases = append(testCases, testCase{
// If the client sees a server with a wrong password, it should
// reject the confirmV value in the ServerHello.
name: "PAKE-Client-WrongPassword",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeWrongPassword,
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Client-Truncate",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
TruncatePAKEMessage: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "PAKE-Server-Truncate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
TruncatePAKEMessage: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: illegal parameter",
})
testCases = append(testCases, testCase{
// Servers may not send CertificateRequest in a PAKE handshake.
name: "PAKE-Client-UnexpectedCertificateRequest",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
ClientAuth: RequireAnyClientCert,
Bugs: ProtocolBugs{
AlwaysSendCertificateRequest: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
// Servers may not send Certificate in a PAKE handshake.
name: "PAKE-Client-UnexpectedCertificate",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
Bugs: ProtocolBugs{
AlwaysSendCertificate: true,
UseCertificateCredential: &rsaCertificate,
// Ignore the client's lack of signature_algorithms.
IgnorePeerSignatureAlgorithmPreferences: true,
},
},
shimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
expectedLocalError: "remote error: unexpected message",
})
testCases = append(testCases, testCase{
// If a server is configured to request client certificates, it should
// still not do so when negotiating a PAKE.
name: "PAKE-Server-DoNotRequestClientCertificate",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
flags: []string{"-require-any-client-certificate"},
})
testCases = append(testCases, testCase{
// Clients should ignore server PAKE credentials.
name: "PAKE-Client-WrongRole",
testType: clientTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeWrongRole},
shouldFail: true,
// The shim will send a non-PAKE ClientHello.
expectedLocalError: "tls: client not configured with PAKE",
})
testCases = append(testCases, testCase{
// Servers should ignore client PAKE credentials.
name: "PAKE-Server-WrongRole",
testType: serverTest,
config: Config{
MinVersion: VersionTLS13,
Credential: &spakeCredential,
},
shimCredentials: []*Credential{&spakeWrongRole},
shouldFail: true,
// The shim will fail the handshake because it has no usable credentials
// available.
expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
expectedLocalError: "remote error: handshake failure",
})
testCases = append(testCases, testCase{
// On the client, we only support a single PAKE credential.
name: "PAKE-Client-MultiplePAKEs",
testType: clientTest,
shimCredentials: []*Credential{&spakeCredential, &spakeWrongPassword},
shouldFail: true,
expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
})
testCases = append(testCases, testCase{
// On the client, we only support a single PAKE credential.
name: "PAKE-Client-PAKEAndCertificate",
testType: clientTest,
shimCredentials: []*Credential{&spakeCredential, &rsaCertificate},
shouldFail: true,
expectedError: ":UNSUPPORTED_CREDENTIAL_LIST:",
})
testCases = append(testCases, testCase{
// We currently do not support resumption with PAKE. Even if configured
// with a session, the client should not offer the session with PAKEs.
name: "PAKE-Client-NoResume",
testType: clientTest,
// Make two connections. For the first connection, just establish a
// session without PAKE, to pick up a session.
config: Config{
Credential: &rsaCertificate,
},
// For the second connection, use SPAKE.
resumeSession: true,
resumeConfig: &Config{
Credential: &spakeCredential,
Bugs: ProtocolBugs{
// Check that the ClientHello does not offer a session, even
// though one was configured.
ExpectNoTLS13PSK: true,
// Respond with an unsolicted PSK extension in ServerHello, to
// check that the client rejects it.
AlwaysSelectPSKIdentity: true,
},
},
resumeShimCredentials: []*Credential{&spakeCredential},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
}
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addPeekTests() {
// Test SSL_peek works, including on empty records.
testCases = append(testCases, testCase{
name: "Peek-Basic",
sendEmptyRecords: 1,
flags: []string{"-peek-then-read"},
})
// Test SSL_peek can drive the initial handshake.
testCases = append(testCases, testCase{
name: "Peek-ImplicitHandshake",
flags: []string{
"-peek-then-read",
"-implicit-handshake",
},
})
// Test SSL_peek can discover and drive a renegotiation.
testCases = append(testCases, testCase{
name: "Peek-Renegotiate",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
flags: []string{
"-peek-then-read",
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
// Test SSL_peek can discover a close_notify.
testCases = append(testCases, testCase{
name: "Peek-Shutdown",
config: Config{
Bugs: ProtocolBugs{
ExpectCloseNotify: true,
},
},
flags: []string{
"-peek-then-read",
"-check-close-notify",
},
})
// Test SSL_peek can discover an alert.
testCases = append(testCases, testCase{
name: "Peek-Alert",
config: Config{
Bugs: ProtocolBugs{
SendSpuriousAlert: alertRecordOverflow,
},
},
flags: []string{"-peek-then-read"},
shouldFail: true,
expectedError: ":TLSV1_ALERT_RECORD_OVERFLOW:",
})
// Test SSL_peek can handle KeyUpdate.
testCases = append(testCases, testCase{
name: "Peek-KeyUpdate",
config: Config{
MaxVersion: VersionTLS13,
},
sendKeyUpdates: 1,
keyUpdateRequest: keyUpdateNotRequested,
flags: []string{"-peek-then-read"},
})
}
+437
View File
@@ -0,0 +1,437 @@
// 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
type perMessageTest struct {
messageType uint8
test testCase
}
// makePerMessageTests returns a series of test templates which cover each
// message in the TLS handshake. These may be used with bugs like
// WrongMessageType to fully test a per-message bug.
func makePerMessageTests() []perMessageTest {
var ret []perMessageTest
// The following tests are limited to TLS 1.2, so QUIC is not tested.
for _, protocol := range []protocol{tls, dtls} {
suffix := "-" + protocol.String()
ret = append(ret, perMessageTest{
messageType: typeClientHello,
test: testCase{
protocol: protocol,
testType: serverTest,
name: "ClientHello" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
if protocol == dtls {
ret = append(ret, perMessageTest{
messageType: typeHelloVerifyRequest,
test: testCase{
protocol: protocol,
name: "HelloVerifyRequest" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
}
ret = append(ret, perMessageTest{
messageType: typeServerHello,
test: testCase{
protocol: protocol,
name: "ServerHello" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificate,
test: testCase{
protocol: protocol,
name: "ServerCertificate" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateStatus,
test: testCase{
protocol: protocol,
name: "CertificateStatus" + suffix,
config: Config{
MaxVersion: VersionTLS12,
Credential: rsaCertificate.WithOCSP(testOCSPResponse),
},
flags: []string{"-enable-ocsp-stapling"},
},
})
ret = append(ret, perMessageTest{
messageType: typeServerKeyExchange,
test: testCase{
protocol: protocol,
name: "ServerKeyExchange" + suffix,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateRequest,
test: testCase{
protocol: protocol,
name: "CertificateRequest" + suffix,
config: Config{
MaxVersion: VersionTLS12,
ClientAuth: RequireAnyClientCert,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeServerHelloDone,
test: testCase{
protocol: protocol,
name: "ServerHelloDone" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificate,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "ClientCertificate" + suffix,
config: Config{
Credential: &rsaCertificate,
MaxVersion: VersionTLS12,
},
flags: []string{"-require-any-client-certificate"},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateVerify,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "CertificateVerify" + suffix,
config: Config{
Credential: &rsaCertificate,
MaxVersion: VersionTLS12,
},
flags: []string{"-require-any-client-certificate"},
},
})
ret = append(ret, perMessageTest{
messageType: typeClientKeyExchange,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "ClientKeyExchange" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
if protocol != dtls {
ret = append(ret, perMessageTest{
messageType: typeNextProtocol,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "NextProtocol" + suffix,
config: Config{
MaxVersion: VersionTLS12,
NextProtos: []string{"bar"},
},
flags: []string{"-advertise-npn", "\x03foo\x03bar\x03baz"},
},
})
ret = append(ret, perMessageTest{
messageType: typeChannelID,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "ChannelID" + suffix,
config: Config{
MaxVersion: VersionTLS12,
ChannelID: &channelIDKey,
},
flags: []string{
"-expect-channel-id",
base64FlagValue(channelIDBytes),
},
},
})
}
ret = append(ret, perMessageTest{
messageType: typeFinished,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "ClientFinished" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeNewSessionTicket,
test: testCase{
protocol: protocol,
name: "NewSessionTicket" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeFinished,
test: testCase{
protocol: protocol,
name: "ServerFinished" + suffix,
config: Config{
MaxVersion: VersionTLS12,
},
},
})
}
for _, protocol := range []protocol{tls, quic, dtls} {
suffix := "-" + protocol.String()
ret = append(ret, perMessageTest{
messageType: typeClientHello,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "TLS13-ClientHello" + suffix,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeServerHello,
test: testCase{
name: "TLS13-ServerHello" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeEncryptedExtensions,
test: testCase{
name: "TLS13-EncryptedExtensions" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateRequest,
test: testCase{
name: "TLS13-CertificateRequest" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
ClientAuth: RequireAnyClientCert,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificate,
test: testCase{
name: "TLS13-ServerCertificate" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateVerify,
test: testCase{
name: "TLS13-ServerCertificateVerify" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeFinished,
test: testCase{
name: "TLS13-ServerFinished" + suffix,
protocol: protocol,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificate,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "TLS13-ClientCertificate" + suffix,
config: Config{
Credential: &rsaCertificate,
MaxVersion: VersionTLS13,
},
flags: []string{"-require-any-client-certificate"},
},
})
ret = append(ret, perMessageTest{
messageType: typeCertificateVerify,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "TLS13-ClientCertificateVerify" + suffix,
config: Config{
Credential: &rsaCertificate,
MaxVersion: VersionTLS13,
},
flags: []string{"-require-any-client-certificate"},
},
})
ret = append(ret, perMessageTest{
messageType: typeFinished,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "TLS13-ClientFinished" + suffix,
config: Config{
MaxVersion: VersionTLS13,
},
},
})
// Only TLS uses EndOfEarlyData.
if protocol == tls {
ret = append(ret, perMessageTest{
messageType: typeEndOfEarlyData,
test: testCase{
testType: serverTest,
protocol: protocol,
name: "TLS13-EndOfEarlyData" + suffix,
config: Config{
MaxVersion: VersionTLS13,
},
resumeSession: true,
earlyData: true,
},
})
}
}
return ret
}
func addWrongMessageTypeTests() {
for _, t := range makePerMessageTests() {
t.test.name = "WrongMessageType-" + t.test.name
if t.test.resumeConfig != nil {
t.test.resumeConfig.Bugs.SendWrongMessageType = t.messageType
} else {
t.test.config.Bugs.SendWrongMessageType = t.messageType
}
t.test.shouldFail = true
t.test.expectedError = ":UNEXPECTED_MESSAGE:"
t.test.expectedLocalError = "remote error: unexpected message"
if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
// In TLS 1.3, if the server believes it has sent ServerHello,
// but the client cannot process it, the client will send an
// unencrypted alert while the server expects encryption. This
// decryption failure is reported differently for each protocol, so
// leave it unchecked.
t.test.expectedLocalError = ""
}
testCases = append(testCases, t.test)
}
}
func addTrailingMessageDataTests() {
for _, t := range makePerMessageTests() {
t.test.name = "TrailingMessageData-" + t.test.name
if t.test.resumeConfig != nil {
t.test.resumeConfig.Bugs.SendTrailingMessageData = t.messageType
} else {
t.test.config.Bugs.SendTrailingMessageData = t.messageType
}
t.test.shouldFail = true
t.test.expectedError = ":DECODE_ERROR:"
t.test.expectedLocalError = "remote error: error decoding message"
if t.test.config.MaxVersion >= VersionTLS13 && t.messageType == typeServerHello {
// In TLS 1.3, if the server believes it has sent ServerHello,
// but the client cannot process it, the client will send an
// unencrypted alert while the server expects encryption. This
// decryption failure is reported differently for each protocol, so
// leave it unchecked.
t.test.expectedLocalError = ""
}
if t.messageType == typeClientHello {
// We have a different error for ClientHello parsing.
t.test.expectedError = ":CLIENTHELLO_PARSE_FAILED:"
}
if t.messageType == typeFinished {
// Bad Finished messages read as the verify data having
// the wrong length.
t.test.expectedError = ":DIGEST_CHECK_FAILED:"
t.test.expectedLocalError = "remote error: error decrypting message"
}
testCases = append(testCases, t.test)
}
}
+546
View File
@@ -0,0 +1,546 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addRenegotiationTests() {
// Servers cannot renegotiate.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-Forbidden",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
expectedLocalError: "remote error: no renegotiation",
})
// The server shouldn't echo the renegotiation extension unless
// requested by the client.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-NoExt",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
RequireRenegotiationInfo: true,
},
},
shouldFail: true,
expectedLocalError: "renegotiation extension missing",
})
// The renegotiation SCSV should be sufficient for the server to echo
// the extension.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Renegotiate-Server-NoExt-SCSV",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
SendRenegotiationSCSV: true,
RequireRenegotiationInfo: true,
},
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FailIfResumeOnRenego: true,
},
},
renegotiate: 1,
// Test renegotiation after both an initial and resumption
// handshake.
resumeSession: true,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
"-expect-secure-renegotiation",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
FailIfResumeOnRenego: true,
},
},
renegotiate: 1,
// Test renegotiation after both an initial and resumption
// handshake.
resumeSession: true,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
"-expect-secure-renegotiation",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-EmptyExt",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
EmptyRenegotiationInfo: true,
},
},
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
expectedLocalError: "handshake failure",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-BadExt",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadRenegotiationInfo: true,
},
},
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
expectedLocalError: "handshake failure",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-BadExt2",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
BadRenegotiationInfoEnd: true,
},
},
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
expectedLocalError: "handshake failure",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Downgrade",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoRenegotiationInfoAfterInitial: true,
},
},
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
expectedLocalError: "handshake failure",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Upgrade",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoRenegotiationInfoInInitial: true,
},
},
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":RENEGOTIATION_MISMATCH:",
expectedLocalError: "handshake failure",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-NoExt-Allowed",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
},
},
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
"-expect-no-secure-renegotiation",
},
})
// Test that the server may switch ciphers on renegotiation without
// problems.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-SwitchCiphers",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
},
renegotiateCiphers: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-SwitchCiphers2",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
renegotiateCiphers: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
// Test that the server may not switch versions on renegotiation.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-SwitchVersion",
config: Config{
MaxVersion: VersionTLS12,
// Pick a cipher which exists at both versions.
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA},
Bugs: ProtocolBugs{
NegotiateVersionOnRenego: VersionTLS11,
// Avoid failing early at the record layer.
SendRecordVersion: VersionTLS12,
},
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
shouldFail: true,
expectedError: ":WRONG_SSL_VERSION:",
})
testCases = append(testCases, testCase{
name: "Renegotiate-SameClientVersion",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS10,
Bugs: ProtocolBugs{
RequireSameRenegoClientVersion: true,
},
},
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-FalseStart",
renegotiate: 1,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
NextProtos: []string{"foo"},
},
flags: []string{
"-false-start",
"-select-next-proto", "foo",
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
shimWritesFirst: true,
})
// Client-side renegotiation controls.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Forbidden-1",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
expectedLocalError: "remote error: no renegotiation",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Once-1",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
flags: []string{
"-renegotiate-once",
"-expect-total-renegotiations", "1",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Freely-1",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Once-2",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 2,
flags: []string{"-renegotiate-once"},
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
expectedLocalError: "remote error: no renegotiation",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Freely-2",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 2,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "2",
},
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-NoIgnore",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendHelloRequestBeforeEveryAppDataRecord: true,
},
},
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
})
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Ignore",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendHelloRequestBeforeEveryAppDataRecord: true,
},
},
flags: []string{
"-renegotiate-ignore",
"-expect-total-renegotiations", "0",
},
})
// Renegotiation may be enabled and then disabled immediately after the
// handshake.
testCases = append(testCases, testCase{
name: "Renegotiate-ForbidAfterHandshake",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
flags: []string{"-forbid-renegotiation-after-handshake"},
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
expectedLocalError: "remote error: no renegotiation",
})
// Renegotiation is not allowed when there is an unfinished write.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-UnfinishedWrite",
config: Config{
MaxVersion: VersionTLS12,
},
renegotiate: 1,
readWithUnfinishedWrite: true,
flags: []string{
"-async",
"-renegotiate-freely",
},
shouldFail: true,
expectedError: ":NO_RENEGOTIATION:",
// We do not successfully send the no_renegotiation alert in
// this case. https://crbug.com/boringssl/130
})
// We reject stray HelloRequests during the handshake in TLS 1.2.
testCases = append(testCases, testCase{
name: "StrayHelloRequest",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendHelloRequestBeforeEveryHandshakeMessage: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
testCases = append(testCases, testCase{
name: "StrayHelloRequest-Packed",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
PackHandshakeFlight: true,
SendHelloRequestBeforeEveryHandshakeMessage: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
// Test that HelloRequest is rejected if it comes in the same record as the
// server Finished.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-Packed",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
PackHandshakeFlight: true,
PackHelloRequestWithFinished: true,
},
},
renegotiate: 1,
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":EXCESS_HANDSHAKE_DATA:",
expectedLocalError: "remote error: unexpected message",
})
// Renegotiation is forbidden in TLS 1.3.
testCases = append(testCases, testCase{
name: "Renegotiate-Client-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendHelloRequestBeforeEveryAppDataRecord: true,
},
},
flags: []string{
"-renegotiate-freely",
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
// Stray HelloRequests during the handshake are forbidden in TLS 1.3.
testCases = append(testCases, testCase{
name: "StrayHelloRequest-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendHelloRequestBeforeEveryHandshakeMessage: true,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_MESSAGE:",
})
// The renegotiation_info extension is not sent in TLS 1.3, but TLS 1.3
// always reads as supporting it, regardless of whether it was
// negotiated.
testCases = append(testCases, testCase{
name: "AlwaysReportRenegotiationInfo-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
NoRenegotiationInfo: true,
},
},
flags: []string{
"-expect-secure-renegotiation",
},
})
// Certificates may not change on renegotiation.
testCases = append(testCases, testCase{
name: "Renegotiation-CertificateChange",
config: Config{
MaxVersion: VersionTLS12,
Credential: &rsaCertificate,
Bugs: ProtocolBugs{
RenegotiationCertificate: &rsaChainCertificate,
},
},
renegotiate: 1,
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":SERVER_CERT_CHANGED:",
})
testCases = append(testCases, testCase{
name: "Renegotiation-CertificateChange-2",
config: Config{
MaxVersion: VersionTLS12,
Credential: &rsaCertificate,
Bugs: ProtocolBugs{
RenegotiationCertificate: &rsa1024Certificate,
},
},
renegotiate: 1,
flags: []string{"-renegotiate-freely"},
shouldFail: true,
expectedError: ":SERVER_CERT_CHANGED:",
})
// We do not negotiate ALPN after the initial handshake. This is
// error-prone and only risks bugs in consumers.
testCases = append(testCases, testCase{
testType: clientTest,
name: "Renegotiation-ForbidALPN",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
// Forcibly negotiate ALPN on both initial and
// renegotiation handshakes. The test stack will
// internally check the client does not offer
// it.
SendALPN: "foo",
},
},
flags: []string{
"-advertise-alpn", "\x03foo\x03bar\x03baz",
"-expect-alpn", "foo",
"-renegotiate-freely",
},
renegotiate: 1,
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
// The server may send different stapled OCSP responses or SCT lists on
// renegotiation, but BoringSSL ignores this and reports the old values.
// Also test that non-fatal verify results are preserved.
testCases = append(testCases, testCase{
testType: clientTest,
name: "Renegotiation-ChangeAuthProperties",
config: Config{
MaxVersion: VersionTLS12,
Credential: rsaCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
Bugs: ProtocolBugs{
SendOCSPResponseOnRenegotiation: testOCSPResponse2,
SendSCTListOnRenegotiation: testSCTList2,
},
},
renegotiate: 1,
flags: []string{
"-renegotiate-freely",
"-expect-total-renegotiations", "1",
"-enable-ocsp-stapling",
"-expect-ocsp-response",
base64FlagValue(testOCSPResponse),
"-enable-signed-cert-timestamps",
"-expect-signed-cert-timestamps",
base64FlagValue(testSCTList),
"-verify-fail",
"-expect-verify-result",
},
})
}
+976
View File
@@ -0,0 +1,976 @@
// 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 "time"
func addResumptionVersionTests() {
for _, sessionVers := range tlsVersions {
for _, resumeVers := range tlsVersions {
protocols := []protocol{tls}
if sessionVers.hasDTLS && resumeVers.hasDTLS {
protocols = append(protocols, dtls)
}
if sessionVers.hasQUIC && resumeVers.hasQUIC {
protocols = append(protocols, quic)
}
for _, protocol := range protocols {
suffix := "-" + sessionVers.name + "-" + resumeVers.name
suffix += "-" + protocol.String()
if sessionVers.version == resumeVers.version {
testCases = append(testCases, testCase{
protocol: protocol,
name: "Resume-Client" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
Bugs: ProtocolBugs{
ExpectNoTLS13PSK: sessionVers.version < VersionTLS13,
},
},
expectations: connectionExpectations{
version: sessionVers.version,
},
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
})
} else if protocol != tls && sessionVers.version >= VersionTLS13 && resumeVers.version < VersionTLS13 {
// In TLS 1.2 and below, the server indicates resumption by echoing
// the client's session ID, which is impossible if the client did
// not send a session ID. If the client offers a TLS 1.3 session, it
// only fills in session ID in TLS (not DTLS or QUIC) for middlebox
// compatibility mode. So, instead, test that the session ID was
// empty and it was indeed impossible to hit this path
testCases = append(testCases, testCase{
protocol: protocol,
name: "Resume-Client-Impossible" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
},
expectations: connectionExpectations{
version: sessionVers.version,
},
resumeConfig: &Config{
MaxVersion: resumeVers.version,
Bugs: ProtocolBugs{
ExpectNoSessionID: true,
},
},
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
expectResumeRejected: true,
})
} else {
// Test that the client rejects ServerHellos which resume
// sessions at inconsistent versions.
expectedError := ":OLD_SESSION_VERSION_NOT_RETURNED:"
if sessionVers.version < VersionTLS13 && resumeVers.version >= VersionTLS13 {
// The server will "resume" the session by sending pre_shared_key,
// but the shim will not have sent pre_shared_key at all. The shim
// should reject this because the extension was not allowed at all.
expectedError = ":UNEXPECTED_EXTENSION:"
}
testCases = append(testCases, testCase{
protocol: protocol,
name: "Resume-Client-Mismatch" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
},
expectations: connectionExpectations{
version: sessionVers.version,
},
resumeConfig: &Config{
MaxVersion: resumeVers.version,
Bugs: ProtocolBugs{
AcceptAnySession: true,
},
},
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
shouldFail: true,
expectedError: expectedError,
})
}
testCases = append(testCases, testCase{
protocol: protocol,
name: "Resume-Client-NoResume" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
},
expectations: connectionExpectations{
version: sessionVers.version,
},
resumeConfig: &Config{
MaxVersion: resumeVers.version,
},
newSessionsOnResume: true,
expectResumeRejected: true,
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Resume-Server" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
},
expectations: connectionExpectations{
version: sessionVers.version,
},
expectResumeRejected: sessionVers != resumeVers,
resumeConfig: &Config{
MaxVersion: resumeVers.version,
Bugs: ProtocolBugs{
SendBothTickets: true,
},
},
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
})
// Repeat the test using session IDs, rather than tickets.
if sessionVers.version < VersionTLS13 && resumeVers.version < VersionTLS13 {
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Resume-Server-NoTickets" + suffix,
resumeSession: true,
config: Config{
MaxVersion: sessionVers.version,
SessionTicketsDisabled: true,
},
expectations: connectionExpectations{
version: sessionVers.version,
},
expectResumeRejected: sessionVers != resumeVers,
resumeConfig: &Config{
MaxVersion: resumeVers.version,
SessionTicketsDisabled: true,
},
resumeExpectations: &connectionExpectations{
version: resumeVers.version,
},
})
}
}
}
}
// Make sure shim ticket mutations are functional.
testCases = append(testCases, testCase{
testType: serverTest,
name: "ShimTicketRewritable",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
FilterTicket: func(in []byte) ([]byte, error) {
in, err := SetShimTicketVersion(in, VersionTLS12)
if err != nil {
return nil, err
}
return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
})
// Resumptions are declined if the version does not match.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-DeclineCrossVersion",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketVersion(in, VersionTLS13)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
expectResumeRejected: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-DeclineCrossVersion-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketVersion(in, VersionTLS12)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
expectResumeRejected: true,
})
// Resumptions are declined if the cipher is invalid or disabled.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-DeclineBadCipher",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
expectResumeRejected: true,
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-DeclineBadCipher-2",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
},
},
},
flags: []string{
"-cipher", "AES128",
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
expectResumeRejected: true,
})
// Sessions are not resumed if they do not use the preferred cipher.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-CipherNotPreferred",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
ExpectNewTicket: true,
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
shouldFail: false,
expectResumeRejected: true,
})
// TLS 1.3 allows sessions to be resumed at a different cipher if their
// PRF hashes match, but BoringSSL will always decline such resumptions.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-CipherNotPreferred-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
FilterTicket: func(in []byte) ([]byte, error) {
// If the client (runner) offers ChaCha20-Poly1305 first, the
// server (shim) always prefers it. Switch it to AES-GCM.
return SetShimTicketCipherSuite(in, TLS_AES_128_GCM_SHA256)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
shouldFail: false,
expectResumeRejected: true,
})
// Sessions may not be resumed if they contain another version's cipher.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-DeclineBadCipher-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
FilterTicket: func(in []byte) ([]byte, error) {
return SetShimTicketCipherSuite(in, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
},
},
},
flags: []string{
"-ticket-key",
base64FlagValue(TestShimTicketKey),
},
expectResumeRejected: true,
})
// If the client does not offer the cipher from the session, decline to
// resume. Clients are forbidden from doing this, but BoringSSL selects
// the cipher first, so we only decline.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-UnofferedCipher",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
},
resumeConfig: &Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
Bugs: ProtocolBugs{
SendCipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
},
},
expectResumeRejected: true,
})
// In TLS 1.3, clients may advertise a cipher list which does not
// include the selected cipher. Test that we tolerate this. Servers may
// resume at another cipher if the PRF matches and are not doing 0-RTT, but
// BoringSSL will always decline.
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-UnofferedCipher-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
Bugs: ProtocolBugs{
SendCipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
},
},
expectResumeRejected: true,
})
// Sessions may not be resumed at a different cipher.
testCases = append(testCases, testCase{
name: "Resume-Client-CipherMismatch",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
},
resumeConfig: &Config{
MaxVersion: VersionTLS12,
CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SendCipherSuite: TLS_RSA_WITH_AES_128_CBC_SHA,
},
},
shouldFail: true,
expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:",
})
// Session resumption in TLS 1.3 may change the cipher suite if the PRF
// matches.
testCases = append(testCases, testCase{
name: "Resume-Client-CipherMismatch-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256},
},
})
// Session resumption in TLS 1.3 is forbidden if the PRF does not match.
testCases = append(testCases, testCase{
name: "Resume-Client-PRFMismatch-TLS13",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
CipherSuites: []uint16{TLS_AES_128_GCM_SHA256},
Bugs: ProtocolBugs{
SendCipherSuite: TLS_AES_256_GCM_SHA384,
},
},
shouldFail: true,
expectedError: ":OLD_SESSION_PRF_HASH_MISMATCH:",
})
for _, secondBinder := range []bool{false, true} {
var suffix string
var defaultCurves []CurveID
if secondBinder {
suffix = "-SecondBinder"
// Force a HelloRetryRequest by predicting an empty curve list.
defaultCurves = []CurveID{}
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-BinderWrongLength" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
SendShortPSKBinder: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: error decrypting message",
expectedError: ":DIGEST_CHECK_FAILED:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-NoPSKBinder" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
SendNoPSKBinder: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: error decoding message",
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-ExtraPSKBinder" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
SendExtraPSKBinder: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: illegal parameter",
expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-ExtraIdentityNoBinder" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
ExtraPSKIdentity: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: illegal parameter",
expectedError: ":PSK_IDENTITY_BINDER_COUNT_MISMATCH:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-InvalidPSKBinder" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
SendInvalidPSKBinder: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: error decrypting message",
expectedError: ":DIGEST_CHECK_FAILED:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-PSKBinderFirstExtension" + suffix,
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: defaultCurves,
Bugs: ProtocolBugs{
PSKBinderFirst: true,
OnlyCorruptSecondPSKBinder: secondBinder,
},
},
shouldFail: true,
expectedLocalError: "remote error: illegal parameter",
expectedError: ":PRE_SHARED_KEY_MUST_BE_LAST:",
})
}
testCases = append(testCases, testCase{
testType: serverTest,
name: "Resume-Server-OmitPSKsOnSecondClientHello",
resumeSession: true,
config: Config{
MaxVersion: VersionTLS13,
DefaultCurves: []CurveID{},
Bugs: ProtocolBugs{
OmitPSKsOnSecondClientHello: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: illegal parameter",
expectedError: ":INCONSISTENT_CLIENT_HELLO:",
})
}
func addSessionTicketTests() {
testCases = append(testCases, testCase{
// In TLS 1.2 and below, empty NewSessionTicket messages
// mean the server changed its mind on sending a ticket.
name: "SendEmptySessionTicket-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendEmptySessionTicket: true,
},
},
flags: []string{"-expect-no-session"},
})
testCases = append(testCases, testCase{
// In TLS 1.3, empty NewSessionTicket messages are not
// allowed.
name: "SendEmptySessionTicket-TLS13",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendEmptySessionTicket: true,
},
},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: error decoding message",
})
// Test that the server ignores unknown PSK modes.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-SendUnknownModeSessionTicket-Server",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendPSKKeyExchangeModes: []byte{0x1a, pskDHEKEMode, 0x2a},
},
},
resumeSession: true,
expectations: connectionExpectations{
version: VersionTLS13,
},
})
// Test that the server does not send session tickets with no matching key exchange mode.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-ExpectNoSessionTicketOnBadKEMode-Server",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendPSKKeyExchangeModes: []byte{0x1a},
ExpectNoNewSessionTicket: true,
},
},
})
// Test that the server does not accept a session with no matching key exchange mode.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-SendBadKEModeSessionTicket-Server",
config: Config{
MaxVersion: VersionTLS13,
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendPSKKeyExchangeModes: []byte{0x1a},
},
},
resumeSession: true,
expectResumeRejected: true,
})
// Test that the server rejects ClientHellos with pre_shared_key but without
// psk_key_exchange_modes.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-SendNoKEMModesWithPSK-Server",
config: Config{
MaxVersion: VersionTLS13,
},
resumeConfig: &Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendPSKKeyExchangeModes: []byte{},
},
},
resumeSession: true,
shouldFail: true,
expectedLocalError: "remote error: missing extension",
expectedError: ":MISSING_EXTENSION:",
})
// Test that the client ticket age is sent correctly.
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-TestValidTicketAge-Client",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectTicketAge: 10 * time.Second,
},
},
resumeSession: true,
flags: []string{
"-resumption-delay", "10",
},
})
// Test that the client ticket age is enforced.
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-TestBadTicketAge-Client",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectTicketAge: 1000 * time.Second,
},
},
resumeSession: true,
shouldFail: true,
expectedLocalError: "tls: invalid ticket age",
})
// Test that the server's ticket age skew reporting works.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Forward",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 15 * time.Second,
},
},
resumeSession: true,
resumeRenewedSession: true,
flags: []string{
"-resumption-delay", "10",
"-expect-ticket-age-skew", "5",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Backward",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 5 * time.Second,
},
},
resumeSession: true,
resumeRenewedSession: true,
flags: []string{
"-resumption-delay", "10",
"-expect-ticket-age-skew", "-5",
},
})
// Test that ticket age skew up to 60 seconds in either direction is accepted.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Forward-60-Accept",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 70 * time.Second,
},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-resumption-delay", "10",
"-expect-ticket-age-skew", "60",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Backward-60-Accept",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 10 * time.Second,
},
},
resumeSession: true,
earlyData: true,
flags: []string{
"-resumption-delay", "70",
"-expect-ticket-age-skew", "-60",
},
})
// Test that ticket age skew beyond 60 seconds in either direction is rejected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Forward-61-Reject",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 71 * time.Second,
},
},
resumeSession: true,
earlyData: true,
expectEarlyDataRejected: true,
flags: []string{
"-resumption-delay", "10",
"-expect-ticket-age-skew", "61",
"-on-resume-expect-early-data-reason", "ticket_age_skew",
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-TicketAgeSkew-Backward-61-Reject",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketAge: 10 * time.Second,
},
},
resumeSession: true,
earlyData: true,
expectEarlyDataRejected: true,
flags: []string{
"-resumption-delay", "71",
"-expect-ticket-age-skew", "-61",
"-on-resume-expect-early-data-reason", "ticket_age_skew",
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-SendTicketEarlyDataSupport",
config: Config{
MaxVersion: VersionTLS13,
MaxEarlyDataSize: 16384,
},
flags: []string{
"-enable-early-data",
"-expect-ticket-supports-early-data",
},
})
// Test that 0-RTT tickets are still recorded as such when early data is disabled overall.
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-SendTicketEarlyDataSupport-Disabled",
config: Config{
MaxVersion: VersionTLS13,
MaxEarlyDataSize: 16384,
},
flags: []string{
"-expect-ticket-supports-early-data",
},
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-DuplicateTicketEarlyDataSupport",
config: Config{
MaxVersion: VersionTLS13,
MaxEarlyDataSize: 16384,
Bugs: ProtocolBugs{
DuplicateTicketEarlyData: true,
},
},
shouldFail: true,
expectedError: ":DUPLICATE_EXTENSION:",
expectedLocalError: "remote error: illegal parameter",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TLS13-ExpectTicketEarlyDataSupport",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
ExpectTicketEarlyData: true,
},
},
flags: []string{
"-enable-early-data",
},
})
// Test that, in TLS 1.3, the server-offered NewSessionTicket lifetime
// is honored.
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-HonorServerSessionTicketLifetime",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketLifetime: 20 * time.Second,
},
},
flags: []string{
"-resumption-delay", "19",
},
resumeSession: true,
})
testCases = append(testCases, testCase{
testType: clientTest,
name: "TLS13-HonorServerSessionTicketLifetime-2",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendTicketLifetime: 20 * time.Second,
// The client should not offer the expired session.
ExpectNoTLS13PSK: true,
},
},
flags: []string{
"-resumption-delay", "21",
},
resumeSession: true,
expectResumeRejected: true,
})
for _, ver := range tlsVersions {
// Prior to TLS 1.3, disabling session tickets enables session IDs.
useStatefulResumption := ver.version < VersionTLS13
// SSL_OP_NO_TICKET implies the server must not mint any tickets.
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-NoTicket-NoMint",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
ExpectNoNewSessionTicket: true,
RequireSessionIDs: useStatefulResumption,
},
},
resumeSession: useStatefulResumption,
flags: []string{"-no-ticket"},
})
// SSL_OP_NO_TICKET implies the server must not accept any tickets.
testCases = append(testCases, testCase{
testType: serverTest,
name: ver.name + "-NoTicket-NoAccept",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
resumeSession: true,
expectResumeRejected: true,
// Set SSL_OP_NO_TICKET on the second connection, after the first
// has established tickets.
flags: []string{"-on-resume-no-ticket"},
})
// SSL_OP_NO_TICKET implies the client must not offer ticket-based
// sessions. The client not only should not send the session ticket
// extension, but if the server echos the session ID, the client should
// reject this.
if ver.version < VersionTLS13 {
testCases = append(testCases, testCase{
name: ver.name + "-NoTicket-NoOffer",
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
},
resumeConfig: &Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
ExpectNoTLS12TicketSupport: true,
// Pretend to accept the session, even though the client
// did not offer it. The client should reject this as
// invalid. A buggy client will still fail because it
// expects resumption, but with a different error.
// Ideally, we would test this by actually resuming the
// previous session, even though the client did not
// provide a ticket.
EchoSessionIDInFullHandshake: true,
},
},
resumeSession: true,
expectResumeRejected: true,
// Set SSL_OP_NO_TICKET on the second connection, after the first
// has established tickets.
flags: []string{"-on-resume-no-ticket"},
shouldFail: true,
expectedError: ":SERVER_ECHOED_INVALID_SESSION_ID:",
expectedLocalError: "remote error: illegal parameter",
})
}
}
}
+1 -21814
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+277
View File
@@ -0,0 +1,277 @@
// 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 "golang.org/x/crypto/cryptobyte"
func trustAnchorListFlagValue(ids ...[]byte) string {
b := cryptobyte.NewBuilder(nil)
for _, id := range ids {
addUint8LengthPrefixedBytes(b, id)
}
return base64FlagValue(b.BytesOrPanic())
}
func addTrustAnchorTests() {
id1 := []byte{1}
id2 := []byte{2, 2}
id3 := []byte{3, 3, 3}
// Unsolicited trust_anchors extensions should be rejected.
testCases = append(testCases, testCase{
name: "TrustAnchors-Unsolicited-Certificate",
config: Config{
MinVersion: VersionTLS13,
Bugs: ProtocolBugs{
AlwaysMatchTrustAnchorID: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-Unsolicited-EncryptedExtensions",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
AlwaysSendAvailableTrustAnchors: true,
},
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
// Test that the client sends trust anchors when configured, and correctly
// reports the server's response.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
"-expect-peer-match-trust-anchor",
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// The client should not like it if the server indicates the match with a non-empty
// extension.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match-Non-Empty-Extension",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
SendNonEmptyTrustAnchorMatch: true,
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
},
shouldFail: true,
expectedLocalError: "remote error: error decoding message",
expectedError: ":ERROR_PARSING_EXTENSION:",
})
// The client should not like it if the server indicates the match on the incorrect
// certificate in the Certificate message.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-Match-On-Incorrect-Certificate",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Credential: rsaChainCertificate.WithTrustAnchorID(id1),
Bugs: ProtocolBugs{
SendTrustAnchorWrongCertificate: true,
ExpectPeerRequestedTrustAnchors: [][]byte{id1, id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id1, id3),
},
shouldFail: true,
expectedLocalError: "remote error: unsupported extension",
expectedError: ":UNEXPECTED_EXTENSION:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequest-NoMatch",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{id3},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(id3),
"-expect-no-peer-match-trust-anchor",
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// An empty trust anchor ID is a syntax error, so most be rejected in both
// ClientHello and EncryptedExtensions.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-EmptyID-ClientHello",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{{}},
},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
testCases = append(testCases, testCase{
name: "TrustAnchors-EmptyID-EncryptedExtensions",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{{}},
},
flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
// Test the server selection logic, as well as whether it correctly reports
// available trust anchors and the match status. (The general selection flow
// is covered in addCertificateSelectionTests.)
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-Match",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id2},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
ExpectPeerMatchTrustAnchor: ptrTo(true),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
rsaCertificate.WithTrustAnchorID(id2),
},
flags: []string{"-expect-selected-credential", "1"},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-None",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id2),
rsaCertificate.WithTrustAnchorID(id3),
},
shouldFail: true,
expectedError: ":NO_MATCHING_ISSUER:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerSelect-Fallback",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{id1},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id2, id3},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id2),
rsaCertificate.WithTrustAnchorID(id3),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "2"},
})
// The ClientHello list may be empty. The client must be able to send it and
// receive available trust anchors.
testCases = append(testCases, testCase{
name: "TrustAnchors-ClientRequestEmpty",
config: Config{
MinVersion: VersionTLS13,
AvailableTrustAnchors: [][]byte{id1, id2},
Bugs: ProtocolBugs{
ExpectPeerRequestedTrustAnchors: [][]byte{},
},
},
flags: []string{
"-requested-trust-anchors", trustAnchorListFlagValue(),
"-expect-peer-available-trust-anchors", trustAnchorListFlagValue(id1, id2),
},
})
// The server must be able to process it, and send available trust anchors.
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-ServerReceiveEmptyRequest",
config: Config{
MinVersion: VersionTLS13,
RequestTrustAnchors: [][]byte{},
Bugs: ProtocolBugs{
ExpectPeerAvailableTrustAnchors: [][]byte{id1, id2},
ExpectPeerMatchTrustAnchor: ptrTo(false),
},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
rsaCertificate.WithTrustAnchorID(id2),
&rsaCertificate,
},
flags: []string{"-expect-selected-credential", "2"},
})
// This extension requires TLS 1.3. If a server receives this and negotiates
// TLS 1.2, it should ignore the extension and not accidentally send
// something in ServerHello (implicitly checked by runner).
testCases = append(testCases, testCase{
testType: serverTest,
name: "TrustAnchors-TLS12-Server",
config: Config{
MaxVersion: VersionTLS12,
RequestTrustAnchors: [][]byte{id1},
},
shimCredentials: []*Credential{
rsaCertificate.WithTrustAnchorID(id1),
&rsaCertificate,
},
// The first credential is skipped because the extension is ignored.
flags: []string{"-expect-selected-credential", "1"},
})
// The client should reject the extension in TLS 1.2 ServerHello.
testCases = append(testCases, testCase{
name: "TrustAnchors-TLS12-Client",
config: Config{
MaxVersion: VersionTLS12,
AvailableTrustAnchors: [][]byte{id1},
Bugs: ProtocolBugs{
AlwaysSendAvailableTrustAnchors: true,
},
},
flags: []string{"-requested-trust-anchors", trustAnchorListFlagValue(id1)},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
expectedLocalError: "remote error: unsupported extension",
})
}
+611
View File
@@ -0,0 +1,611 @@
// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package runner
func addVersionNegotiationTests() {
for _, protocol := range []protocol{tls, dtls, quic} {
for _, shimVers := range allVersions(protocol) {
// Assemble flags to disable all newer versions on the shim.
var flags []string
for _, vers := range allVersions(protocol) {
if vers.version > shimVers.version {
flags = append(flags, vers.excludeFlag)
}
}
flags2 := []string{"-max-version", shimVers.shimFlag(protocol)}
// Test configuring the runner's maximum version.
for _, runnerVers := range allVersions(protocol) {
expectedVersion := shimVers.version
if runnerVers.version < shimVers.version {
expectedVersion = runnerVers.version
}
suffix := shimVers.name + "-" + runnerVers.name
suffix += "-" + protocol.String()
// Determine the expected initial record-layer versions.
clientVers := shimVers.version
if clientVers > VersionTLS10 {
clientVers = VersionTLS10
}
clientVers = recordVersionToWire(clientVers, protocol)
serverVers := expectedVersion
if expectedVersion >= VersionTLS13 {
serverVers = VersionTLS12
}
serverVers = recordVersionToWire(serverVers, protocol)
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "VersionNegotiation-Client-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: clientVers,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "VersionNegotiation-Client2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: clientVers,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiation-Server-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: serverVers,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiation-Server2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
ExpectInitialRecordVersion: serverVers,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
})
}
}
}
// Test the version extension at all versions.
for _, protocol := range []protocol{tls, dtls, quic} {
for _, vers := range allVersions(protocol) {
suffix := vers.name + "-" + protocol.String()
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "VersionNegotiationExtension-" + suffix,
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111, vers.wire(protocol), 0x2222},
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: vers.version,
},
})
}
}
// If all versions are unknown, negotiation fails.
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSupportedVersions",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111},
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "NoSupportedVersions-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{0x1111},
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ClientHelloVersionTooHigh",
config: Config{
MaxVersion: VersionTLS13,
Bugs: ProtocolBugs{
SendClientVersion: 0x0304,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ConflictingVersionNegotiation",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: VersionTLS12,
SendSupportedVersions: []uint16{VersionTLS11},
IgnoreTLS13DowngradeRandom: true,
},
},
// The extension takes precedence over the ClientHello version.
expectations: connectionExpectations{
version: VersionTLS11,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "ConflictingVersionNegotiation-2",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: VersionTLS11,
SendSupportedVersions: []uint16{VersionTLS12},
IgnoreTLS13DowngradeRandom: true,
},
},
// The extension takes precedence over the ClientHello version.
expectations: connectionExpectations{
version: VersionTLS12,
},
})
// Test that TLS 1.2 isn't negotiated by the supported_versions extension in
// the ServerHello.
testCases = append(testCases, testCase{
testType: clientTest,
name: "SupportedVersionSelection-TLS12",
config: Config{
MaxVersion: VersionTLS12,
Bugs: ProtocolBugs{
SendServerSupportedVersionExtension: VersionTLS12,
},
},
shouldFail: true,
expectedError: ":UNEXPECTED_EXTENSION:",
})
// Test that the maximum version is selected regardless of the
// client-sent order.
testCases = append(testCases, testCase{
testType: serverTest,
name: "IgnoreClientVersionOrder",
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{VersionTLS12, VersionTLS13},
},
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
// Test for version tolerance.
testCases = append(testCases, testCase{
testType: serverTest,
name: "MinorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x03ff,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "MajorVersionTolerance",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0400,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
// TLS 1.3 must be negotiated with the supported_versions
// extension, not ClientHello.version.
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "VersionTolerance-TLS13",
config: Config{
Bugs: ProtocolBugs{
// Although TLS 1.3 does not use
// ClientHello.version, it still tolerates high
// values there.
SendClientVersion: 0x0400,
},
},
expectations: connectionExpectations{
version: VersionTLS13,
},
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "MinorVersionTolerance-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xfe00,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "MajorVersionTolerance-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xfdff,
OmitSupportedVersions: true,
IgnoreTLS13DowngradeRandom: true,
},
},
expectations: connectionExpectations{
version: VersionTLS12,
},
})
// Test that versions below 3.0 are rejected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "VersionTooLow",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0x0200,
OmitSupportedVersions: true,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
protocol: dtls,
testType: serverTest,
name: "VersionTooLow-DTLS",
config: Config{
Bugs: ProtocolBugs{
SendClientVersion: 0xffff,
OmitSupportedVersions: true,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
testCases = append(testCases, testCase{
name: "ServerBogusVersion",
config: Config{
Bugs: ProtocolBugs{
SendServerHelloVersion: 0x1234,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
})
// Test TLS 1.3's downgrade signal.
for _, protocol := range []protocol{tls, dtls} {
for _, vers := range allVersions(protocol) {
if vers.version >= VersionTLS13 {
continue
}
clientShimError := "tls: downgrade from TLS 1.3 detected"
if vers.version < VersionTLS12 {
clientShimError = "tls: downgrade from TLS 1.2 detected"
}
// for _, test := range downgradeTests {
// The client should enforce the downgrade sentinel.
testCases = append(testCases, testCase{
protocol: protocol,
name: "Downgrade-" + vers.name + "-Client-" + protocol.String(),
config: Config{
Bugs: ProtocolBugs{
NegotiateVersion: vers.wire(protocol),
},
},
expectations: connectionExpectations{
version: vers.version,
},
shouldFail: true,
expectedError: ":TLS13_DOWNGRADE:",
expectedLocalError: "remote error: illegal parameter",
})
// The server should emit the downgrade signal.
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "Downgrade-" + vers.name + "-Server-" + protocol.String(),
config: Config{
Bugs: ProtocolBugs{
SendSupportedVersions: []uint16{vers.wire(protocol)},
},
},
expectations: connectionExpectations{
version: vers.version,
},
shouldFail: true,
expectedLocalError: clientShimError,
})
}
}
// SSL 3.0 support has been removed. Test that the shim does not
// support it.
testCases = append(testCases, testCase{
name: "NoSSL3-Client",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
},
shouldFail: true,
expectedLocalError: "tls: client did not offer any supported protocol versions",
})
testCases = append(testCases, testCase{
name: "NoSSL3-Client-Unsolicited",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
Bugs: ProtocolBugs{
// The above test asserts the client does not
// offer SSL 3.0 in the supported_versions
// list. Additionally assert that it rejects an
// unsolicited SSL 3.0 ServerHello.
NegotiateVersion: VersionSSL30,
},
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
expectedLocalError: "remote error: protocol version not supported",
})
testCases = append(testCases, testCase{
testType: serverTest,
name: "NoSSL3-Server",
config: Config{
MinVersion: VersionSSL30,
MaxVersion: VersionSSL30,
},
shouldFail: true,
expectedError: ":UNSUPPORTED_PROTOCOL:",
expectedLocalError: "remote error: protocol version not supported",
})
}
func addMinimumVersionTests() {
for _, protocol := range []protocol{tls, dtls, quic} {
for _, shimVers := range allVersions(protocol) {
// Assemble flags to disable all older versions on the shim.
var flags []string
for _, vers := range allVersions(protocol) {
if vers.version < shimVers.version {
flags = append(flags, vers.excludeFlag)
}
}
flags2 := []string{"-min-version", shimVers.shimFlag(protocol)}
for _, runnerVers := range allVersions(protocol) {
suffix := shimVers.name + "-" + runnerVers.name
suffix += "-" + protocol.String()
var expectedVersion uint16
var shouldFail bool
var expectedError, expectedLocalError string
if runnerVers.version >= shimVers.version {
expectedVersion = runnerVers.version
} else {
shouldFail = true
expectedError = ":UNSUPPORTED_PROTOCOL:"
expectedLocalError = "remote error: protocol version not supported"
}
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "MinimumVersion-Client-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
// Ensure the server does not decline to
// select a version (versions extension) or
// cipher (some ciphers depend on versions).
NegotiateVersion: runnerVers.wire(protocol),
IgnorePeerCipherPreferences: shouldFail,
},
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: clientTest,
name: "MinimumVersion-Client2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
Bugs: ProtocolBugs{
// Ensure the server does not decline to
// select a version (versions extension) or
// cipher (some ciphers depend on versions).
NegotiateVersion: runnerVers.wire(protocol),
IgnorePeerCipherPreferences: shouldFail,
},
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "MinimumVersion-Server-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
// The version name check does not recognize the
// |excludeFlag| construction in |flags|.
skipVersionNameCheck: true,
})
testCases = append(testCases, testCase{
protocol: protocol,
testType: serverTest,
name: "MinimumVersion-Server2-" + suffix,
config: Config{
MaxVersion: runnerVers.version,
},
flags: flags2,
expectations: connectionExpectations{
version: expectedVersion,
},
shouldFail: shouldFail,
expectedError: expectedError,
expectedLocalError: expectedLocalError,
})
}
}
}
}
func addRecordVersionTests() {
for _, ver := range tlsVersions {
// Test that the record version is enforced.
testCases = append(testCases, testCase{
name: "CheckRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendRecordVersion: 0x03ff,
},
},
shouldFail: true,
expectedError: ":WRONG_VERSION_NUMBER:",
})
// Test that the ClientHello may use any record version, for
// compatibility reasons.
testCases = append(testCases, testCase{
testType: serverTest,
name: "LooseInitialRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendInitialRecordVersion: 0x03ff,
},
},
})
// Test that garbage ClientHello record versions are rejected.
testCases = append(testCases, testCase{
testType: serverTest,
name: "GarbageInitialRecordVersion-" + ver.name,
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendInitialRecordVersion: 0xffff,
},
},
shouldFail: true,
expectedError: ":WRONG_VERSION_NUMBER:",
})
}
}