| // 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, |
| }) |
| } |
| } |