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