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