This is an automated email from the ASF dual-hosted git repository.
kezhuw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git
The following commit(s) were added to refs/heads/master by this push:
new d7f9717ad ZOOKEEPER-4929: Make c client side cert optional in
connecting to tls server
d7f9717ad is described below
commit d7f9717adf46addb582c0e0d90bf03a6ac6666af
Author: Edward Seabrook <[email protected]>
AuthorDate: Mon Aug 18 19:41:16 2025 +0100
ZOOKEEPER-4929: Make c client side cert optional in connecting to tls server
Reviewers: kezhuw
Author: eseabrook1
Closes #2257 from eseabrook1/relax_client_certificate_requirements
---
.../zookeeper-client-c/include/zookeeper.h | 32 +++++++++++++++
.../zookeeper-client-c/src/zookeeper.c | 45 ++++++++++++----------
.../zookeeper-client-c/tests/TestClient.cc | 33 +++++++++++++---
.../zookeeper-client-c/tests/zkServer.sh | 4 +-
zookeeper-client/zookeeper-client-c/tests/zoo.cfg | 1 +
5 files changed, 88 insertions(+), 27 deletions(-)
diff --git a/zookeeper-client/zookeeper-client-c/include/zookeeper.h
b/zookeeper-client/zookeeper-client-c/include/zookeeper.h
index 6d73ccd09..ead328a94 100644
--- a/zookeeper-client/zookeeper-client-c/include/zookeeper.h
+++ b/zookeeper-client/zookeeper-client-c/include/zookeeper.h
@@ -537,6 +537,38 @@ ZOOAPI zhandle_t *zookeeper_init(const char *host,
watcher_fn fn,
int recv_timeout, const clientid_t *clientid, void *context, int flags);
#ifdef HAVE_OPENSSL_H
+/**
+ * \brief create a handle to communicate with zookeeper using SSL.
+ *
+ * This method creates a new handle and a zookeeper session that corresponds
+ * to that handle. Session establishment is asynchronous, meaning that the
+ * session should not be considered established until (and unless) an
+ * event of state ZOO_CONNECTED_STATE is received.
+ * \param host comma separated host:port pairs, each corresponding to a zk
+ * server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002"
+ * \param cert SSL certificate string. Two formats are supported: server CA
+ * only e.g. "/path/to/server_ca.cer" and server CA with client certificate
e.g.
+ *
"/path/to/server_ca.cer,/path/to/client_cert.pem,/path/to/client_cert.key,client_cert_password".
+ * \param fn the global watcher callback function. When notifications are
+ * triggered this function will be invoked.
+ * \param recv_timeout the timeout for the zookeeper session.
+ * \param clientid the id of a previously established session that this
+ * client will be reconnecting to. Pass 0 if not reconnecting to a previous
+ * session. Clients can access the session id of an established, valid,
+ * connection by calling \ref zoo_client_id. If the session corresponding to
+ * the specified clientid has expired, or if the clientid is invalid for
+ * any reason, the returned zhandle_t will be invalid -- the zhandle_t
+ * state will indicate the reason for failure (typically
+ * ZOO_EXPIRED_SESSION_STATE).
+ * \param context the handback object that will be associated with this
instance
+ * of zhandle_t. Application can access it (for example, in the watcher
+ * callback) using \ref zoo_get_context. The object is not used by zookeeper
+ * internally and can be null.
+ * \param flags reserved for future use. Should be set to zero.
+ * \return a pointer to the opaque zhandle structure. If it fails to create
+ * a new zhandle the function returns NULL and the errno variable
+ * indicates the reason.
+ */
ZOOAPI zhandle_t *zookeeper_init_ssl(const char *host, const char *cert,
watcher_fn fn,
int recv_timeout, const clientid_t *clientid, void *context, int flags);
#endif
diff --git a/zookeeper-client/zookeeper-client-c/src/zookeeper.c
b/zookeeper-client/zookeeper-client-c/src/zookeeper.c
index c59da18a5..ce7deb5e2 100644
--- a/zookeeper-client/zookeeper-client-c/src/zookeeper.c
+++ b/zookeeper-client/zookeeper-client-c/src/zookeeper.c
@@ -2777,27 +2777,30 @@ static int init_ssl_for_socket(zsock_t *fd, zhandle_t
*zh, int fail_on_error) {
errno = EINVAL;
return ZBADARGUMENTS;
}
- /*CLIENT CA FILE (With Certificate Chain)*/
- if (SSL_CTX_use_certificate_chain_file(*ctx, fd->cert->cert) != 1) {
- SSL_CTX_free(*ctx);
- LOG_ERROR(LOGCALLBACK(zh), "Failed to load client certificate
chain from %s", fd->cert->cert);
- errno = EINVAL;
- return ZBADARGUMENTS;
- }
- /*CLIENT PRIVATE KEY*/
- SSL_CTX_set_default_passwd_cb_userdata(*ctx, fd->cert->passwd);
- if (SSL_CTX_use_PrivateKey_file(*ctx, fd->cert->key, SSL_FILETYPE_PEM)
!= 1) {
- SSL_CTX_free(*ctx);
- LOG_ERROR(LOGCALLBACK(zh), "Failed to load client private key from
%s", fd->cert->key);
- errno = EINVAL;
- return ZBADARGUMENTS;
- }
- /*CHECK*/
- if (SSL_CTX_check_private_key(*ctx) != 1) {
- SSL_CTX_free(*ctx);
- LOG_ERROR(LOGCALLBACK(zh), "SSL_CTX_check_private_key failed");
- errno = EINVAL;
- return ZBADARGUMENTS;
+ if (fd->cert->cert != NULL && fd->cert->passwd != NULL &&
fd->cert->key != NULL)
+ {
+ /*CLIENT CA FILE (With Certificate Chain)*/
+ if (SSL_CTX_use_certificate_chain_file(*ctx, fd->cert->cert) != 1)
{
+ SSL_CTX_free(*ctx);
+ LOG_ERROR(LOGCALLBACK(zh), "Failed to load client certificate
chain from %s", fd->cert->cert);
+ errno = EINVAL;
+ return ZBADARGUMENTS;
+ }
+ /*CLIENT PRIVATE KEY*/
+ SSL_CTX_set_default_passwd_cb_userdata(*ctx, fd->cert->passwd);
+ if (SSL_CTX_use_PrivateKey_file(*ctx, fd->cert->key,
SSL_FILETYPE_PEM) != 1) {
+ SSL_CTX_free(*ctx);
+ LOG_ERROR(LOGCALLBACK(zh), "Failed to load client private key
from %s", fd->cert->key);
+ errno = EINVAL;
+ return ZBADARGUMENTS;
+ }
+ /*CHECK*/
+ if (SSL_CTX_check_private_key(*ctx) != 1) {
+ SSL_CTX_free(*ctx);
+ LOG_ERROR(LOGCALLBACK(zh), "SSL_CTX_check_private_key failed");
+ errno = EINVAL;
+ return ZBADARGUMENTS;
+ }
}
/*MULTIPLE HANDSHAKE*/
SSL_CTX_set_mode(*ctx, SSL_MODE_AUTO_RETRY);
diff --git a/zookeeper-client/zookeeper-client-c/tests/TestClient.cc
b/zookeeper-client/zookeeper-client-c/tests/TestClient.cc
index 8c79c01b0..83a7203d7 100644
--- a/zookeeper-client/zookeeper-client-c/tests/TestClient.cc
+++ b/zookeeper-client/zookeeper-client-c/tests/TestClient.cc
@@ -212,6 +212,10 @@ class Zookeeper_simpleSystem : public
CPPUNIT_NS::TestFixture
#endif
#ifdef HAVE_OPENSSL_H
CPPUNIT_TEST(testSSL);
+ CPPUNIT_TEST(testSSLNoClientCert);
+
+ // Order after above two tests as it changes ssl.clientAuth
+ CPPUNIT_TEST(testSSLServerOnly);
#endif
CPPUNIT_TEST(testCreate);
CPPUNIT_TEST(testCreateContainer);
@@ -314,6 +318,12 @@ public:
CPPUNIT_ASSERT(system(cmd) == 0);
}
+ void startServer(const char *envs) {
+ char cmd[1024];
+ sprintf(cmd, "%s %s start %s", envs, ZKSERVER_CMD, getHostPorts());
+ CPPUNIT_ASSERT(system(cmd) == 0);
+ }
+
void stopServer() {
char cmd[1024];
sprintf(cmd, "%s stop %s", ZKSERVER_CMD, getHostPorts());
@@ -879,15 +889,28 @@ public:
}
#ifdef HAVE_OPENSSL_H
- void testSSL() {
+ int checkSSL(const char *path, const char *certs) {
watchctx_t ctx;
zoo_set_debug_level(ZOO_LOG_LEVEL_DEBUG);
- zhandle_t *zk = createSSLClient("127.0.0.1:22281",
"/tmp/certs/server.crt,/tmp/certs/client.crt,/tmp/certs/clientkey.pem,password",
&ctx);
+ zhandle_t *zk = createSSLClient("127.0.0.1:22281", certs, &ctx);
CPPUNIT_ASSERT(zk);
int rc = 0;
- rc = zoo_create(zk, "/ssl", NULL, -1,
- &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0);
- CPPUNIT_ASSERT_EQUAL((int) ZOK, rc);
+ return zoo_create(zk, path, NULL, -1, &ZOO_OPEN_ACL_UNSAFE, 0, 0, 0);
+ }
+
+ void testSSL() {
+ CPPUNIT_ASSERT_EQUAL((int) ZOK, checkSSL("/ssl",
"/tmp/certs/server.crt,/tmp/certs/client.crt,/tmp/certs/clientkey.pem,password"));
+ }
+
+ void testSSLNoClientCert() {
+ CPPUNIT_ASSERT(ZOK != checkSSL("/ssl-no-client-cert",
"/tmp/certs/server.crt"));
+ }
+
+ void testSSLServerOnly() {
+ stopServer();
+ startServer("CLIENT_AUTH=none");
+
+ CPPUNIT_ASSERT_EQUAL((int) ZOK, checkSSL("/ssl-server-only",
"/tmp/certs/server.crt"));
}
#endif
diff --git a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
index 4e090a3cd..e1b850dd1 100755
--- a/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
+++ b/zookeeper-client/zookeeper-client-c/tests/zkServer.sh
@@ -24,6 +24,8 @@ ZOOPORT=${ZOOPORT:-"22181"}
# Some tests are setting the maxClientConnections. When it is not set, we
fallback to default 100
ZKMAXCNXNS=${ZKMAXCNXNS:-"100"}
+CLIENT_AUTH=${CLIENT_AUTH:-"need"}
+
EXTRA_JVM_ARGS=${EXTRA_JVM_ARGS:-""}
if [[ -z $1 ]]; then
@@ -163,7 +165,7 @@ case $1 in
)
# ===== prepare the configs
- sed
"s#TMPDIR#$tmp_dir#g;s#CERTDIR#$certs_dir#g;s#MAXCLIENTCONNECTIONS#$ZKMAXCNXNS#g;s#CLIENTPORT#$ZOOPORT#g"
"$tests_dir/zoo.cfg" >"$tmp_dir/zoo.cfg"
+ sed
"s#TMPDIR#$tmp_dir#g;s#CERTDIR#$certs_dir#g;s#MAXCLIENTCONNECTIONS#$ZKMAXCNXNS#g;s#CLIENTPORT#$ZOOPORT#g;s#CLIENT_AUTH#$CLIENT_AUTH#g"
"$tests_dir/zoo.cfg" >"$tmp_dir/zoo.cfg"
if [[ -n $read_only ]]; then
# we can put the new server to read-only mode by starting only a single
instance of a three node server
{
diff --git a/zookeeper-client/zookeeper-client-c/tests/zoo.cfg
b/zookeeper-client/zookeeper-client-c/tests/zoo.cfg
index d7444238e..5e3df6264 100644
--- a/zookeeper-client/zookeeper-client-c/tests/zoo.cfg
+++ b/zookeeper-client/zookeeper-client-c/tests/zoo.cfg
@@ -8,6 +8,7 @@ localSessionsEnabled=true
clientPort=CLIENTPORT
secureClientPort=22281
serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory
+ssl.clientAuth=CLIENT_AUTH
ssl.keyStore.location=CERTDIR/server.jks
ssl.keyStore.password=password
ssl.trustStore.location=CERTDIR/servertrust.jks