Skip to content

ZOOKEEPER-4929: Make c client side cert optional in connecting to tls server #2257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions zookeeper-client/zookeeper-client-c/include/zookeeper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 24 additions & 21 deletions zookeeper-client/zookeeper-client-c/src/zookeeper.c
Original file line number Diff line number Diff line change
Expand Up @@ -2769,27 +2769,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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe need SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); in client when skip cert.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not particularly familiar with the SSL APIs, but my assumption was that we would be in client mode, and still want to validate the server certificate so the existing settings are still correct. We only want the TLS handshake to continue if ths server certificate is valid

https://docs.openssl.org/master/man3/SSL_CTX_set_verify/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a server side option ssl.clientAuth.

I plan to construct a test for this pr.

Copy link
Member

@kezhuw kezhuw Jun 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @eseabrook1, I created eseabrook1#1 to add test case for this pr. Would you mind take a look ? This pr will be updated automatically once you merged that pr.

{
/*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);
Expand Down