| // 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 := flagInts("-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 := flagInts("-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"}, | |
| flagInts("-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, | |
| } | |
| // 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)}, | |
| flagInts("-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(flagInts("-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 == "", | |
| }) | |
| } | |
| } | |
| // 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"}, | |
| flagInts("-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, | |
| shouldFail: true, | |
| expectedError: expectedError, | |
| }) | |
| shimFlags = append([]string{"-verify-peer"}, | |
| flagInts("-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, | |
| }) | |
| 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", | |
| }) | |
| } | |
| // Test that RPK client cert verification fails if we force it to fail. | |
| shimFlags = append([]string{"-require-any-client-certificate", "-verify-fail"}, | |
| flagInts("-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:", | |
| }) | |
| } | |
| } | |
| func addRawPublicKeyTests() { | |
| addServerCertTypeTests() | |
| addClientCertTypeTests() | |
| } |