From 2e863b6b96e24eb64d138c9ec70c5326d9184f58 Mon Sep 17 00:00:00 2001 From: Ivan Kozlovic Date: Tue, 6 Aug 2024 14:33:15 -0600 Subject: [PATCH] [ADDED] TLS: natsOptions_SetTLSHandshakeFirst() This is to force a client to perform the TLS handshake first, that is, not wait for the INFO protocol from the server. This is needed if the server is configured to require clients to perform the TLS handshake first (before sending the INFO protocol). Resolves #779 Signed-off-by: Ivan Kozlovic --- src/conn.c | 20 ++++++++++++-- src/nats.h | 21 +++++++++++++-- src/natsp.h | 1 + src/opts.c | 21 +++++++++++++++ test/list_test.txt | 1 + test/test.c | 67 +++++++++++++++++++++++++++++++++++++++++++++- test/tlsfirst.conf | 16 +++++++++++ 7 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 test/tlsfirst.conf diff --git a/src/conn.c b/src/conn.c index 7cfe9beb1..710c0dd34 100644 --- a/src/conn.c +++ b/src/conn.c @@ -786,7 +786,12 @@ _checkForSecure(natsConnection *nc) } if ((s == NATS_OK) && nc->opts->secure) - s = _makeTLSConn(nc); + { + // If TLS handshake first is true, we have already done + // the handshake, so do it only if false. + if (!nc->opts->tlsHandshakeFirst) + s = _makeTLSConn(nc); + } return NATS_UPDATE_ERR_STACK(s); } @@ -1968,8 +1973,14 @@ _processConnInit(natsConnection *nc) nc->status = NATS_CONN_STATUS_CONNECTING; + // If we need to have a TLS connection and want the TLS handshake to occur + // first, do it now. + if (nc->opts->secure && nc->opts->tlsHandshakeFirst) + s = _makeTLSConn(nc); + // Process the INFO protocol that we should be receiving - s = _processExpectedInfo(nc); + if (s == NATS_OK) + s = _processExpectedInfo(nc); // Send the CONNECT and PING protocol, and wait for the PONG. if (s == NATS_OK) @@ -3280,6 +3291,11 @@ natsConn_create(natsConnection **newConn, natsOptions *options) nc->sockCtx.fd = NATS_SOCK_INVALID; nc->opts = options; + // If the TLSHandshakeFirst option is specified, make sure that + // the Secure boolean is true. + if (nc->opts->tlsHandshakeFirst) + nc->opts->secure = true; + nc->errStr[0] = '\0'; s = natsMutex_Create(&(nc->mu)); diff --git a/src/nats.h b/src/nats.h index dc331e08f..295af3a30 100644 --- a/src/nats.h +++ b/src/nats.h @@ -1043,7 +1043,7 @@ typedef struct jsConsumerNamesList */ typedef struct jsConsumerPauseResponse { - bool Paused; + bool Paused; int64_t PauseUntil; ///< UTC time expressed as number of nanoseconds since epoch. int64_t PauseRemaining; ///< Remaining time in nanoseconds. } jsConsumerPauseResponse; @@ -2327,6 +2327,23 @@ natsOptions_SetName(natsOptions *opts, const char *name); NATS_EXTERN natsStatus natsOptions_SetSecure(natsOptions *opts, bool secure); +/** \brief Performs TLS handshake first. + * + * If the server is not configured to require the client to perform + * the TLS handshake first, the server sends an INFO protocol first. + * When receiving it, the client and server are then initiate the + * TLS handshake. + * + * If the server is configured to require the client to perform + * the TLS handshake first, the client will fail to connect if + * not setting this option. Conversely, if the client is configured + * with this option but the server is not, the connection will fail. + * + * @param opts the pointer to the #natsOptions object. + */ +NATS_EXTERN natsStatus +natsOptions_TLSHandshakeFirst(natsOptions *opts); + /** \brief Loads the trusted CA certificates from a file. * * Loads the trusted CA certificates from a file. @@ -4039,7 +4056,7 @@ natsConnection_Connect(natsConnection **nc, natsOptions *options); * This means that all subscriptions and consumers should be resubscribed and * their work resumed after successful reconnect where all reconnect options are * respected. - * + * * @param nc the pointer to the #natsConnection object. */ natsStatus diff --git a/src/natsp.h b/src/natsp.h index b9887b5d3..1f7894ecd 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -224,6 +224,7 @@ struct __natsOptions bool pedantic; bool allowReconnect; bool secure; + bool tlsHandshakeFirst; int ioBufSize; int maxReconnect; int64_t reconnectWait; diff --git a/src/opts.c b/src/opts.c index 1c7864635..078691a38 100644 --- a/src/opts.c +++ b/src/opts.c @@ -363,6 +363,21 @@ natsOptions_SetSecure(natsOptions *opts, bool secure) return NATS_UPDATE_ERR_STACK(s); } +natsStatus +natsOptions_TLSHandshakeFirst(natsOptions *opts) +{ + natsStatus s = NATS_OK; + + LOCK_AND_CHECK_OPTIONS(opts, 0); + + opts->tlsHandshakeFirst = true; + opts->secure = true; + + UNLOCK_OPTS(opts); + + return NATS_UPDATE_ERR_STACK(s); +} + natsStatus natsOptions_LoadCATrustedCertificates(natsOptions *opts, const char *fileName) { @@ -689,6 +704,12 @@ natsOptions_SetSecure(natsOptions *opts, bool secure) return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); } +natsStatus +natsOptions_TLSHandshakeFirst(natsOptions *opts) +{ + return nats_setError(NATS_ILLEGAL_STATE, "%s", NO_SSL_ERR); +} + natsStatus natsOptions_LoadCATrustedCertificates(natsOptions *opts, const char *fileName) { diff --git a/test/list_test.txt b/test/list_test.txt index 0804b84fb..4b2bd6cf0 100644 --- a/test/list_test.txt +++ b/test/list_test.txt @@ -246,6 +246,7 @@ _test(SSLBasic) _test(SSLCertAndKeyFromMemory) _test(SSLCiphers) _test(SSLConnectVerboseOption) +_test(SSLHandshakeFirst) _test(SSLLoadCAFromMemory) _test(SSLMultithreads) _test(SSLReconnectWithAuthError) diff --git a/test/test.c b/test/test.c index cf0d3716a..6deaf0daa 100644 --- a/test/test.c +++ b/test/test.c @@ -2614,7 +2614,8 @@ void test_natsOptions(void) && (opts->writeDeadline == natsLib_defaultWriteDeadline()) && !opts->noEcho && !opts->retryOnFailedConnect - && !opts->ignoreDiscoveredServers) + && !opts->ignoreDiscoveredServers + && !opts->tlsHandshakeFirst); test("Add URL: "); s = natsOptions_SetURL(opts, "test"); @@ -2764,6 +2765,14 @@ void test_natsOptions(void) testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false)); #endif + test("Set TLSHandshakeFirst: "); + s = natsOptions_TLSHandshakeFirst(opts); +#if defined(NATS_HAS_TLS) + testCond((s == NATS_OK) && (opts->tlsHandshakeFirst == true) && (opts->secure == true)); +#else + testCond((s == NATS_ILLEGAL_STATE) && (opts->secure == false) && (opts->tlsHandshakeFirst == false)); +#endif + test("Set Pedantic: "); s = natsOptions_SetPedantic(opts, true); testCond((s == NATS_OK) && (opts->pedantic == true)); @@ -21197,6 +21206,62 @@ void test_SSLConnectVerboseOption(void) #endif } +void test_SSLHandshakeFirst(void) +{ +#if defined(NATS_HAS_TLS) + natsStatus s; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsPid serverPid = NATS_INVALID_PID; + + serverPid = _startServer("nats://127.0.0.1:4443", "-config tlsfirst.conf", true); + CHECK_SERVER_STARTED(serverPid); + + test("Set options: "); + s = natsOptions_Create(&opts); + IFOK(s, natsOptions_SetURL(opts, "nats://127.0.0.1:4443")); + IFOK(s, natsOptions_SetSecure(opts, true)); + IFOK(s, natsOptions_SkipServerVerification(opts, true)); + IFOK(s, natsOptions_SetTimeout(opts, 500)); + testCond(s == NATS_OK); + + test("Check that connect fails if option not set: "); + s = natsConnection_Connect(&nc, opts); + testCond(s != NATS_OK); + nats_clearLastError(); + + test("Set TLSHandshakeFirst option error: "); + s = natsOptions_TLSHandshakeFirst(NULL); + testCond(s == NATS_INVALID_ARG); + nats_clearLastError(); + + test("Set TLSHandshakeFirst option: "); + s = natsOptions_TLSHandshakeFirst(opts); + testCond(s == NATS_OK); + + test("Check that connect succeeds: "); + s = natsConnection_Connect(&nc, opts); + testCond(s == NATS_OK); + natsConnection_Destroy(nc); + nc = NULL; + + _stopServer(serverPid); + serverPid = _startServer("nats://127.0.0.1:4443", "-config tls.conf", true); + CHECK_SERVER_STARTED(serverPid); + + test("Check that connect fails if option is set but not in the server: "); + s = natsConnection_Connect(&nc, opts); + testCond(s != NATS_OK); + nats_clearLastError(); + + natsOptions_Destroy(opts); + +#else + test("Skipped when built with no SSL support: "); + testCond(true); +#endif +} + #if defined(NATS_HAS_TLS) static natsStatus _elDummyAttach(void **userData, void *loop, natsConnection *nc, natsSock socket) { return NATS_OK; } diff --git a/test/tlsfirst.conf b/test/tlsfirst.conf new file mode 100644 index 000000000..5ed521c17 --- /dev/null +++ b/test/tlsfirst.conf @@ -0,0 +1,16 @@ + +# Simple TLS config file + +port: 4443 +net: "0.0.0.0" + +tls { + # Server cert + cert_file: "certs/server-cert.pem" + # Server private key + key_file: "certs/server-key.pem" + # Increase timeout for valgrind tests + timeout: 2 + # Force client to do the handshake first + handshake_first: true +}