blob: 3208b592065a3fc0c80d3397e2fd35e48c561dcf [file]
// Copyright 2026 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/sha256"
"fmt"
"slices"
"strconv"
)
const certTypeBogus CertificateType = 5
var (
certTypesListRPKOnly = []CertificateType{certTypeRawPublicKey}
certTypesListRPKX509 = []CertificateType{certTypeRawPublicKey, certTypeX509}
certTypesListX509RPK = []CertificateType{certTypeX509, certTypeRawPublicKey}
certTypesListX509Only = []CertificateType{certTypeX509}
certTypesListUnknown = []CertificateType{certTypeBogus}
certTypesListUnknownX509 = []CertificateType{certTypeX509, certTypeBogus}
certTypesListRPKUnknown = []CertificateType{certTypeRawPublicKey, certTypeBogus}
)
// A malformed RPK credential which is sent on the wire as an empty SubjectPublicKeyInfo.
var rpkEmpty = Credential{
Type: CredentialTypeRawPublicKey,
Certificate: [][]byte{{}},
}
func addRPKCustomVerifyToFlags(peerCredential *Credential, shimFlags []string) []string {
if peerCredential.Type == CredentialTypeRawPublicKey {
peerRPKHash := sha256.Sum256(peerCredential.Certificate[0])
shimFlags = append(shimFlags, "-expect-peer-rpk-sha256", base64FlagValue(peerRPKHash[:]))
}
return shimFlags
}
func addServerCertTypeTests() {
for _, ver := range allVersions(tls) {
// Tests sending list of accepted server cert types in the ClientHello and
// receiving the server's negotiated cert type and credential.
for _, test := range []struct {
name string
serverCertTypesAccepted []CertificateType
expectedClientHelloExtension []CertificateType
serverHelloExtension []CertificateType
serverCredential *Credential
expectedError string
expectedLocalError string
}{
{
name: "RPKOnly-NegotiatedRPK",
serverCertTypesAccepted: certTypesListRPKOnly,
expectedClientHelloExtension: certTypesListRPKOnly,
serverHelloExtension: certTypesListRPKOnly,
serverCredential: &rpkEcdsaP256,
},
{
name: "RPKX509-NegotiatedRPK",
serverCertTypesAccepted: certTypesListRPKX509,
expectedClientHelloExtension: certTypesListRPKX509,
serverHelloExtension: certTypesListRPKOnly,
serverCredential: &rpkEcdsaP256,
},
{
name: "RPKX509-NegotiatedX509",
serverCertTypesAccepted: certTypesListRPKX509,
expectedClientHelloExtension: certTypesListRPKX509,
serverHelloExtension: certTypesListX509Only,
serverCredential: &ecdsaP256Certificate,
},
{
name: "RPKX509-NegotiatedX509ByDefault",
serverCertTypesAccepted: certTypesListRPKX509,
expectedClientHelloExtension: certTypesListRPKX509,
serverHelloExtension: nil,
serverCredential: &ecdsaP256Certificate,
},
{
name: "X509RPK-NegotiatedRPK",
serverCertTypesAccepted: certTypesListX509RPK,
expectedClientHelloExtension: certTypesListX509RPK,
serverHelloExtension: certTypesListRPKOnly,
serverCredential: &rpkEcdsaP256,
},
{
name: "X509RPK-NegotiatedX509",
serverCertTypesAccepted: certTypesListX509RPK,
expectedClientHelloExtension: certTypesListX509RPK,
serverHelloExtension: certTypesListX509Only,
serverCredential: &ecdsaP256Certificate,
},
{
name: "X509RPK-NegotiatedX509ByDefault",
serverCertTypesAccepted: certTypesListX509RPK,
expectedClientHelloExtension: certTypesListX509RPK,
serverHelloExtension: nil,
serverCredential: &ecdsaP256Certificate,
},
{
// Configuring the default cert type only omits the extension.
name: "DefaultOnly-Omitted",
serverCertTypesAccepted: certTypesListX509Only,
expectedClientHelloExtension: []CertificateType{},
serverHelloExtension: nil,
serverCredential: &ecdsaP256Certificate,
},
{
// Server picked RPK despite client not supporting RPKs. Client should
// reject the unsolicited extension.
name: "DefaultOnly-ServerPickedRPKInError",
serverCertTypesAccepted: certTypesListX509Only,
expectedClientHelloExtension: []CertificateType{},
serverHelloExtension: certTypesListRPKOnly,
serverCredential: &rpkEcdsaP256,
expectedError: ":UNEXPECTED_EXTENSION:",
},
{
// Server picked X.509 despite client not supporting X.509. Client
// should reject the invalid extension.
name: "RPKOnly-ServerPickedX509InError",
serverCertTypesAccepted: certTypesListRPKOnly,
expectedClientHelloExtension: certTypesListRPKOnly,
serverHelloExtension: certTypesListX509Only,
serverCredential: &ecdsaP256Certificate,
expectedError: ":UNSUPPORTED_CERTIFICATE:",
},
{
// Server picked X.509 despite client not supporting X.509. Client
// should reject.
name: "RPKOnly-ServerPickedX509ByDefaultInError",
serverCertTypesAccepted: certTypesListRPKOnly,
expectedClientHelloExtension: certTypesListRPKOnly,
serverHelloExtension: nil,
serverCredential: &ecdsaP256Certificate,
expectedError: ":UNSUPPORTED_CERTIFICATE:",
},
{
// Server sent an X.509 cert despite negotiating RPK. It should fail to
// parse.
name: "RPKOnly-NegotiatedRPK-ServerIncorrectlySentX509",
serverCertTypesAccepted: certTypesListRPKOnly,
expectedClientHelloExtension: certTypesListRPKOnly,
serverHelloExtension: certTypesListRPKOnly,
serverCredential: &ecdsaP256Certificate,
expectedError: ":DECODE_ERROR:",
},
{
// Server sent a RPK despite negotiating X.509 (explicitly). It should
// fail to parse.
name: "RPKX509-NegotiatedX509-ServerIncorrectlySentRPK",
serverCertTypesAccepted: certTypesListRPKX509,
expectedClientHelloExtension: certTypesListRPKX509,
serverHelloExtension: certTypesListX509Only,
serverCredential: &rpkEcdsaP256,
expectedLocalError: "remote error: error decoding message",
},
{
// Server sent a RPK despite negotiating X.509 (by default). It should
// fail to parse.
name: "RPKX509-NegotiatedX509ByDefault-ServerIncorrectlySentRPK",
serverCertTypesAccepted: certTypesListRPKX509,
expectedClientHelloExtension: certTypesListRPKX509,
serverHelloExtension: nil,
serverCredential: &rpkEcdsaP256,
expectedLocalError: "remote error: error decoding message",
},
} {
shimFlags := flagCertTypes("-accepted-peer-cert-types", test.serverCertTypesAccepted)
if test.expectedError == "" {
shimFlags = append(shimFlags, "-expect-peer-certificate-type", strconv.Itoa(int(test.serverCredential.Type.CertificateType())))
shimFlags = addRPKCustomVerifyToFlags(test.serverCredential, shimFlags)
}
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ServerCertificateType-Client-Requests%s-%s", test.name, ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: test.serverCredential,
Bugs: ProtocolBugs{
ExpectServerCertificateTypes: test.expectedClientHelloExtension,
SendServerCertificateTypes: test.serverHelloExtension,
},
},
flags: shimFlags,
shouldFail: test.expectedError != "" || test.expectedLocalError != "",
expectedError: test.expectedError,
expectedLocalError: test.expectedLocalError,
resumeSession: test.expectedError == "" && test.expectedLocalError == "",
})
}
shimFlags := flagCertTypes("-accepted-peer-cert-types", certTypesListRPKOnly)
// The Certificate message contains an RPK with an empty SPKI. In TLS 1.2
// this is considered to indicate the lack of a certificate (so the client
// will reject), whereas this is illegal for the TLS 1.3 RPK Certificate
// format.
expectedError := ":INVALID_RAW_PUBLIC_KEY:"
if ver.version <= VersionTLS12 {
// Parsing the Certificate failed to yield a credential.
expectedError = ":DECODE_ERROR:"
}
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ServerCertificateType-Client-RequestsRPKOnly-ServerSentEmptyRPK-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEmpty,
Bugs: ProtocolBugs{
ExpectServerCertificateTypes: certTypesListRPKOnly,
SendServerCertificateTypes: certTypesListRPKOnly,
},
},
flags: shimFlags,
shouldFail: true,
expectedError: expectedError,
})
if ver.version >= VersionTLS13 {
// If the server sends a Certificate message for an RPK that contains an
// empty certificate list, client should reject. (For TLS 1.2 and below,
// this test would be equivalent to the one above.)
expectedError = ":PEER_DID_NOT_RETURN_A_CERTIFICATE:"
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ServerCertificateType-Client-RequestsRPKOnly-ServerSentEmptyCertificateList-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
ExpectServerCertificateTypes: certTypesListRPKOnly,
SendServerCertificateTypes: certTypesListRPKOnly,
EmptyCertificateList: true,
SkipCertificateVerify: true,
},
},
flags: shimFlags,
shouldFail: true,
expectedError: expectedError,
})
// If the server sends an otherwise valid Certificate message with an RPK,
// but does not send CertificateVerify, client should reject.
shimFlags = addRPKCustomVerifyToFlags(&rpkEcdsaP256, shimFlags)
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ServerCertificateType-Client-RPKWithoutCertificateVerify-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
ExpectServerCertificateTypes: certTypesListRPKOnly,
SendServerCertificateTypes: certTypesListRPKOnly,
SkipCertificateVerify: true,
},
},
flags: shimFlags,
shouldFail: true,
expectedLocalError: "remote error: unexpected message",
})
}
// Test that RPK server cert verification fails if we force it to fail.
shimFlags = append([]string{"-verify-fail"},
flagCertTypes("-accepted-peer-cert-types", certTypesListRPKOnly)...)
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ServerCertificateType-Client-RPKVerifyFail-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
ExpectServerCertificateTypes: certTypesListRPKOnly,
SendServerCertificateTypes: certTypesListRPKOnly,
},
},
flags: shimFlags,
shouldFail: true,
expectedError: ":CERTIFICATE_VERIFY_FAILED:",
})
// Tests that server can receive a server_certificate_type extension from
// the client and select and send its most-preferred shared cert type based
// on configured server credentials, and test that server sends credential
// matching the selected cert type if appropriate.
for _, test := range []struct {
name string
serverCertTypesRequested []CertificateType
serverCredentialsConfigured []*Credential
expectedCredentialIndex int
}{
{
name: "RPKRequested-RPKAvailable",
serverCertTypesRequested: certTypesListRPKOnly,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256},
expectedCredentialIndex: 0,
},
{
// The first matching credential is picked, in the absence of other
// criteria. (See also: Server-RawPublicKey-* tests in
// certificate_selection_tests.go.)
name: "RPKRequested-MultipleRPKsAvailable",
serverCertTypesRequested: certTypesListRPKOnly,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256, &rpkRsa},
expectedCredentialIndex: 0,
},
{
name: "RPKX509Requested-RPKAvailable",
serverCertTypesRequested: certTypesListRPKX509,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256},
expectedCredentialIndex: 0,
},
{
name: "X509RPKRequested-RPKAvailable",
serverCertTypesRequested: certTypesListX509RPK,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256},
expectedCredentialIndex: 0,
},
{
name: "RPKRequested-RPKX509Available",
serverCertTypesRequested: certTypesListRPKOnly,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedCredentialIndex: 0,
},
{
name: "RPKX509Requested-RPKX509Available",
serverCertTypesRequested: certTypesListRPKX509,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedCredentialIndex: 0,
},
{
name: "X509RPKRequested-RPKX509Available",
serverCertTypesRequested: certTypesListX509RPK,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedCredentialIndex: 0,
},
{
name: "RPKRequested-X509RPKAvailable",
serverCertTypesRequested: certTypesListRPKOnly,
serverCredentialsConfigured: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedCredentialIndex: 1,
},
{
name: "RPKX509Requested-X509RPKAvailable",
serverCertTypesRequested: certTypesListRPKX509,
serverCredentialsConfigured: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedCredentialIndex: 0,
},
{
name: "X509RPKRequested-X509RPKAvailable",
serverCertTypesRequested: certTypesListX509RPK,
serverCredentialsConfigured: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedCredentialIndex: 0,
},
{
// The server should ignore any values from the client that are unknown,
// and use the remaining values in the list.
name: "RPKUnknownRequested-X509RPKAvailable",
serverCertTypesRequested: certTypesListRPKUnknown,
serverCredentialsConfigured: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedCredentialIndex: 1,
},
{
// If the only known value in the list received from the client is the
// default X.509, it's still valid if it wasn't the only value.
name: "UnknownX509Requested-X509RPKAvailable",
serverCertTypesRequested: certTypesListUnknownX509,
serverCredentialsConfigured: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedCredentialIndex: 1,
},
} {
var expectedServerCredential *Credential
if test.expectedCredentialIndex != -1 {
expectedServerCredential = test.serverCredentialsConfigured[test.expectedCredentialIndex]
}
serverTestCase := testCase{
testType: serverTest,
name: fmt.Sprintf("ServerCertificateType-Server-%s-%s", test.name, ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendServerCertificateTypes: test.serverCertTypesRequested,
ExpectServerCertificateTypes: []CertificateType{expectedServerCredential.Type.CertificateType()},
},
},
flags: []string{
"-on-initial-expect-selected-credential", strconv.Itoa(test.expectedCredentialIndex),
},
expectations: connectionExpectations{
peerCertificate: expectedServerCredential,
},
shimCredentials: test.serverCredentialsConfigured,
resumeSession: true,
skipSplitHandshake: true,
}
// Test that the server can defer configuring credentials to the cert
// callback.
certCallbackTestCase := serverTestCase
certCallbackTestCase.flags = append(slices.Clip(certCallbackTestCase.flags),
"-async")
certCallbackTestCase.name += "-CertCallback"
// Test that the server can defer configuring credentials to the early
// callback.
earlyCallbackTestCase := serverTestCase
earlyCallbackTestCase.flags = append(slices.Clip(earlyCallbackTestCase.flags),
"-async", "-use-early-callback")
earlyCallbackTestCase.name += "-EarlyCallback"
testCases = append(testCases,
serverTestCase,
certCallbackTestCase,
earlyCallbackTestCase,
)
}
// The server should reject a client's list that contains only the default
// X.509, which is a syntax error.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ServerCertificateType-Server-RejectsDefaultOnly-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Bugs: ProtocolBugs{
SendServerCertificateTypes: certTypesListX509Only,
},
},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
})
}
}
func addClientCertTypeTests() {
for _, ver := range allVersions(tls) {
// Tests sending client_certificate_type extension in the ClientHello based
// on configured client credentials, and test that client responds to
// server's selected cert type by sending the credential if appropriate.
for _, test := range []struct {
name string
clientCredentials []*Credential
expectedClientHelloExtension []CertificateType
serverSelectedClientCertType []CertificateType
skipCertificateRequest bool
expectedCredentialIndex int
expectedFailure string
}{
{
name: "RPKOnly-SendsRequestedRPK",
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "RPKOnly-ServerRequestsX509InError",
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNSUPPORTED_CERTIFICATE:",
},
{
name: "RPKOnly-ServerOmitsExtension",
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: []CertificateType{},
expectedFailure: ":UNKNOWN_CERTIFICATE_TYPE:",
},
// If the server omits the extension and thus requests X.509 by default,
// it is not an error if the server doesn't send a CertificateRequest
// after all.
{
name: "RPKOnly-ServerOmitsExtension-NoCertRequest",
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: []CertificateType{},
skipCertificateRequest: true,
expectedCredentialIndex: -1,
},
{
name: "MultipleRPKs-SendsFirstRPK",
clientCredentials: []*Credential{&rpkEcdsaP256, &rpkRsa},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "RPKX509-SendsRequestedRPK",
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListRPKX509,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "RPKX509-SendsRequestedX509",
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListRPKX509,
serverSelectedClientCertType: certTypesListX509Only,
expectedCredentialIndex: 1,
},
{
name: "RPKX509-SendsDefaultX509",
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListRPKX509,
serverSelectedClientCertType: []CertificateType{},
expectedCredentialIndex: 1,
},
{
name: "X509RPK-SendsRequestedRPK",
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListX509RPK,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 1,
},
{
name: "X509RPK-SendsRequestedX509",
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListX509RPK,
serverSelectedClientCertType: certTypesListX509Only,
expectedCredentialIndex: 0,
},
{
name: "X509RPK-SendsDefaultX509",
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListX509RPK,
serverSelectedClientCertType: []CertificateType{},
expectedCredentialIndex: 0,
},
} {
clientAuth := RequestClientCert
if test.skipCertificateRequest {
clientAuth = NoClientCert
}
var expectedClientCredential *Credential
if test.expectedFailure == "" && test.expectedCredentialIndex != -1 {
expectedClientCredential = test.clientCredentials[test.expectedCredentialIndex]
}
testCases = append(testCases, testCase{
testType: clientTest,
name: fmt.Sprintf("ClientCertificateType-Client-Offers%s-%s", test.name, ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: clientAuth,
Bugs: ProtocolBugs{
ExpectClientCertificateTypes: test.expectedClientHelloExtension,
SendClientCertificateTypes: test.serverSelectedClientCertType,
},
},
shimCredentials: test.clientCredentials,
flags: []string{
"-on-initial-expect-selected-credential", strconv.Itoa(test.expectedCredentialIndex),
},
expectations: connectionExpectations{
peerCertificate: expectedClientCredential,
},
shouldFail: test.expectedFailure != "",
expectedError: test.expectedFailure,
resumeSession: test.expectedFailure == "",
})
}
// Tests that overriding the default client_certificate_type logic works, and
// client can explicitly configure types to send in the ClientHello, and test
// that client responds to server's selected cert type by sending the
// credential if appropriate.
for _, test := range []struct {
name string
configuredClientCertTypes []CertificateType
clientCredentials []*Credential
expectedClientHelloExtension []CertificateType
serverSelectedClientCertType []CertificateType
skipCertificateRequest bool
expectedCredentialIndex int
expectedFailure string
}{
{
name: "RPKOnly-ConfiguredAsOnlyCredential-SendsRequestedRPK",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "RPKOnly-ConfiguredAsOnlyCredential-ServerRequestsX509InError",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNSUPPORTED_CERTIFICATE:",
},
{
name: "RPKOnly-ConfiguredAsFirstCredential-SendsRequestedRPK",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "RPKOnly-ConfiguredAsFirstCredential-ServerRequestsX509InError",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNSUPPORTED_CERTIFICATE:",
},
{
name: "RPKOnly-ConfiguredAsSecondCredential-SendsRequestedRPK",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 1,
},
{
name: "RPKOnly-ConfiguredAsSecondCredential-ServerRequestsX509InError",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNSUPPORTED_CERTIFICATE:",
},
{
name: "RPKX509-ConfiguredInOppositeOrder-SendsRequestedRPK",
configuredClientCertTypes: certTypesListRPKX509,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKX509,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 1,
},
{
name: "RPKX509-ConfiguredInOppositeOrder-SendsRequestedX509",
configuredClientCertTypes: certTypesListRPKX509,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: certTypesListRPKX509,
serverSelectedClientCertType: certTypesListX509Only,
expectedCredentialIndex: 0,
},
{
name: "X509RPK-ConfiguredInOppositeOrder-SendsRequestedRPK",
configuredClientCertTypes: certTypesListX509RPK,
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListX509RPK,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: 0,
},
{
name: "X509RPK-ConfiguredInOppositeOrder-SendsRequestedX509",
configuredClientCertTypes: certTypesListX509RPK,
clientCredentials: []*Credential{&rpkEcdsaP256, &ecdsaP256Certificate},
expectedClientHelloExtension: certTypesListX509RPK,
serverSelectedClientCertType: certTypesListX509Only,
expectedCredentialIndex: 1,
},
{
name: "DefaultX509Only-ServerRequestsX509InError",
configuredClientCertTypes: certTypesListX509Only,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: []CertificateType{},
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNEXPECTED_EXTENSION:",
},
{
name: "DefaultX509Only-ServerRequestsRPKInError",
configuredClientCertTypes: certTypesListX509Only,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: []CertificateType{},
serverSelectedClientCertType: certTypesListRPKOnly,
expectedFailure: ":UNEXPECTED_EXTENSION:",
},
{
name: "DefaultX509Only-SendsDefaultX509",
configuredClientCertTypes: certTypesListX509Only,
clientCredentials: []*Credential{&ecdsaP256Certificate, &rpkEcdsaP256},
expectedClientHelloExtension: []CertificateType{},
serverSelectedClientCertType: []CertificateType{},
expectedCredentialIndex: 0,
},
// The client falsely advertises an RPK and the server selects it. There
// is no such RPK to send, but the client should be able to proceed
// without a cert.
{
name: "RPK-NotActuallyConfigured-ServerRequestsRPK",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListRPKOnly,
expectedCredentialIndex: -1,
},
{
name: "RPK-NotActuallyConfigured-ServerRequestsX509InError",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: certTypesListX509Only,
expectedFailure: ":UNSUPPORTED_CERTIFICATE:",
},
// If the server omits the extension and thus requests X.509 by default,
// it is not an error if the server doesn't send a CertificateRequest
// after all.
{
name: "RPK-NotActuallyConfigured-ServerOmitsExtension-NoCertRequest",
configuredClientCertTypes: certTypesListRPKOnly,
clientCredentials: []*Credential{},
expectedClientHelloExtension: certTypesListRPKOnly,
serverSelectedClientCertType: []CertificateType{},
skipCertificateRequest: true,
expectedCredentialIndex: -1,
},
} {
clientAuth := RequestClientCert
if test.skipCertificateRequest {
clientAuth = NoClientCert
}
var expectedClientCredential *Credential
if test.expectedFailure == "" && test.expectedCredentialIndex != -1 {
expectedClientCredential = test.clientCredentials[test.expectedCredentialIndex]
}
clientTestCase := testCase{
testType: clientTest,
name: fmt.Sprintf("ClientCertificateType-Client-Explicit-Offers%s-%s", test.name, ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
ClientAuth: clientAuth,
Bugs: ProtocolBugs{
ExpectClientCertificateTypes: test.expectedClientHelloExtension,
SendClientCertificateTypes: test.serverSelectedClientCertType,
},
},
flags: append(
[]string{"-on-initial-expect-selected-credential", strconv.Itoa(test.expectedCredentialIndex)},
flagCertTypes("-available-client-cert-types", test.configuredClientCertTypes)...),
shimCredentials: test.clientCredentials,
expectations: connectionExpectations{
peerCertificate: expectedClientCredential,
},
shouldFail: test.expectedFailure != "",
expectedError: test.expectedFailure,
resumeSession: test.expectedFailure == "",
}
// Test that the client can defer configuring credentials to the cert
// callback.
certCallbackTestCase := clientTestCase
certCallbackTestCase.flags = append(slices.Clip(certCallbackTestCase.flags), "-async")
certCallbackTestCase.name += "-CertCallback"
testCases = append(testCases,
clientTestCase,
certCallbackTestCase,
)
}
// Tests receiving a client_certificate_type extension from the client and
// selecting and sending our most-preferred shared cert type.
for _, test := range []struct {
name string
clientCertTypesReceived []CertificateType
clientCertTypesAccepted []CertificateType
expectedServerHelloExtension []CertificateType
clientCredential *Credential
expectedError string
expectedLocalError string
}{
{
name: "RPKReceived-RPKAccepted",
clientCertTypesReceived: certTypesListRPKOnly,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
name: "RPKX509Received-RPKAccepted",
clientCertTypesReceived: certTypesListRPKX509,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
name: "X509RPKReceived-RPKAccepted",
clientCertTypesReceived: certTypesListX509RPK,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
name: "RPKX509Received-RPKX509Accepted",
clientCertTypesReceived: certTypesListRPKX509,
clientCertTypesAccepted: certTypesListRPKX509,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
name: "X509RPKReceived-RPKX509Accepted",
clientCertTypesReceived: certTypesListX509RPK,
clientCertTypesAccepted: certTypesListRPKX509,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
name: "RPKX509Received-X509RPKAccepted",
clientCertTypesReceived: certTypesListRPKX509,
clientCertTypesAccepted: certTypesListX509RPK,
expectedServerHelloExtension: certTypesListX509Only,
clientCredential: &ecdsaP256Certificate,
},
{
name: "X509RPKReceived-X509RPKAccepted",
clientCertTypesReceived: certTypesListX509RPK,
clientCertTypesAccepted: certTypesListX509RPK,
expectedServerHelloExtension: certTypesListX509Only,
clientCredential: &ecdsaP256Certificate,
},
{
name: "RejectsInvalidEmptyExtension",
clientCertTypesReceived: []CertificateType{},
clientCertTypesAccepted: certTypesListX509RPK,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: illegal parameter",
},
{
// The client should have omitted the extension if only the default is
// accepted.
name: "RejectsInvalidDefaultOnly",
clientCertTypesReceived: certTypesListX509Only,
clientCertTypesAccepted: certTypesListX509RPK,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: illegal parameter",
},
{
// The client's list contains only an unknown value, which is ignored.
// Negotiating a client cert type value fails.
name: "IgnoresUnknownValue-NoOtherType",
clientCertTypesReceived: certTypesListUnknown,
clientCertTypesAccepted: certTypesListX509RPK,
expectedError: ":UNSUPPORTED_CERTIFICATE:",
expectedLocalError: "remote error: unsupported certificate",
},
{
// The client's list contains an unknown value, which is ignored, and
// a recognized value, which is not shared with the server.
name: "IgnoresUnknownValue-NoSharedType",
clientCertTypesReceived: certTypesListUnknownX509,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedError: ":UNSUPPORTED_CERTIFICATE:",
expectedLocalError: "remote error: unsupported certificate",
},
{
// The client's list contains an unknown value, which is ignored, and
// a recognized value, which is accepted successfully.
name: "IgnoresUnknownValue-RPKAccepted",
clientCertTypesReceived: certTypesListRPKUnknown,
clientCertTypesAccepted: certTypesListX509RPK,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &rpkEcdsaP256,
},
{
// If the client does not send the extension, the server should treat it
// as X.509 only by default.
name: "NoClientHelloCertTypes-SelectsX509ByDefault",
clientCertTypesReceived: nil,
clientCertTypesAccepted: certTypesListRPKX509,
expectedServerHelloExtension: []CertificateType{},
clientCredential: &ecdsaP256Certificate,
},
{
// If the client does not send the extension, but the server is
// configured to only accept RPKs, the connection should fail.
name: "NoClientHelloCertTypes-NoSharedType",
clientCertTypesReceived: nil,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedError: ":UNSUPPORTED_CERTIFICATE:",
expectedLocalError: "remote error: unsupported certificate",
},
{
name: "RPKReceived-RPKAccepted-ClientSentX509InError",
clientCertTypesReceived: certTypesListRPKOnly,
clientCertTypesAccepted: certTypesListRPKOnly,
expectedServerHelloExtension: certTypesListRPKOnly,
clientCredential: &ecdsaP256Certificate,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: error decoding message",
},
} {
for _, verifyMode := range []struct {
name string
flag string
}{
{"FailIfNoClientCert", "-require-any-client-certificate"},
{"VerifyPeer", "-verify-peer"},
} {
shimFlags :=
append(flagCertTypes("-accepted-peer-cert-types", test.clientCertTypesAccepted),
verifyMode.flag)
if test.expectedError == "" {
shimFlags = append(shimFlags,
"-expect-peer-certificate-type", strconv.Itoa(int(test.clientCredential.Type.CertificateType())))
shimFlags = addRPKCustomVerifyToFlags(test.clientCredential, shimFlags)
}
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ClientCertificateType-Server-%s-%s-%s", test.name, verifyMode.name, ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: test.clientCredential,
Bugs: ProtocolBugs{
SendClientCertificateTypes: test.clientCertTypesReceived,
ExpectClientCertificateTypes: test.expectedServerHelloExtension,
},
},
flags: shimFlags,
shouldFail: test.expectedError != "" || test.expectedLocalError != "",
expectedError: test.expectedError,
expectedLocalError: test.expectedLocalError,
resumeSession: test.expectedError == "" && test.expectedLocalError == "",
skipSplitHandshake: true,
})
}
}
// The Certificate message contains an RPK with an empty SPKI. In TLS 1.2
// this is considered to indicate the lack of a certificate (so a server
// configured to require a client cert will reject), whereas this is
// illegal for the TLS 1.3 RPK Certificate format.
shimFlags := append([]string{"-require-any-client-certificate"},
flagCertTypes("-accepted-peer-cert-types", certTypesListRPKOnly)...)
expectedError := ":INVALID_RAW_PUBLIC_KEY:"
if ver.version <= VersionTLS12 {
expectedError = ":PEER_DID_NOT_RETURN_A_CERTIFICATE:"
}
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ClientCertificateType-Server-ClientSentEmptyRPK-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEmpty,
Bugs: ProtocolBugs{
SendClientCertificateTypes: certTypesListRPKOnly,
ExpectClientCertificateTypes: certTypesListRPKOnly,
},
},
flags: shimFlags,
skipSplitHandshake: true,
shouldFail: true,
expectedError: expectedError,
})
shimFlags = append([]string{"-verify-peer"},
flagCertTypes("-accepted-peer-cert-types", certTypesListRPKOnly)...)
// If the client sends a Certificate message for an RPK that contains an
// empty certificate list, and the server isn't configured to require a
// client cert, it should proceed without a client cert.
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ClientCertificateType-Server-ClientSentEmptyCertificateList-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
SendClientCertificateTypes: certTypesListRPKOnly,
ExpectClientCertificateTypes: certTypesListRPKOnly,
EmptyCertificateList: true,
SkipCertificateVerify: true,
},
},
flags: shimFlags,
skipSplitHandshake: true,
})
if ver.version >= VersionTLS13 {
// If the client sends an otherwise valid Certificate message with an RPK,
// but does not send CertificateVerify, server should reject.
shimFlags = addRPKCustomVerifyToFlags(&rpkEcdsaP256, shimFlags)
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ClientCertificateType-Server-RPKWithoutCertificateVerify-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
SendClientCertificateTypes: certTypesListRPKOnly,
ExpectClientCertificateTypes: certTypesListRPKOnly,
SkipCertificateVerify: true,
},
},
flags: shimFlags,
shouldFail: true,
expectedLocalError: "remote error: unexpected message",
skipSplitHandshake: true,
})
}
// Test that RPK client cert verification fails if we force it to fail.
shimFlags = append([]string{"-require-any-client-certificate", "-verify-fail"},
flagCertTypes("-accepted-peer-cert-types", certTypesListRPKOnly)...)
testCases = append(testCases, testCase{
testType: serverTest,
name: fmt.Sprintf("ClientCertificateType-Server-RPKVerifyFail-%s", ver.name),
config: Config{
MinVersion: ver.version,
MaxVersion: ver.version,
Credential: &rpkEcdsaP256,
Bugs: ProtocolBugs{
SendClientCertificateTypes: certTypesListRPKOnly,
ExpectClientCertificateTypes: certTypesListRPKOnly,
},
},
flags: shimFlags,
shouldFail: true,
expectedError: ":CERTIFICATE_VERIFY_FAILED:",
skipSplitHandshake: true,
})
}
}
func addRawPublicKeyTests() {
addServerCertTypeTests()
addClientCertTypeTests()
}