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..48cf966f8 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; } @@ -21271,8 +21336,11 @@ void test_SSLReconnectWithAuthError(void) IFOK(s, natsOptions_SetTimeout(opts, 250)); IFOK(s, natsOptions_SetMaxReconnect(opts, 1000)); IFOK(s, natsOptions_SetReconnectWait(opts, 100)); + IFOK(s, natsOptions_SetReconnectJitter(opts, 0, 0)); IFOK(s, natsOptions_SetClosedCB(opts, _closedCb, (void*) &args)); IFOK(s, natsOptions_SetServers(opts, (const char*[2]){"tls://user:pwd@127.0.0.1:4443", "tls://bad:pwd@127.0.0.1:4444"}, 2)); + IFOK(s, natsOptions_SetNoRandomize(opts, true)); + IFOK(s, natsOptions_SetIgnoreDiscoveredServers(opts, true)); if (opts == NULL) FAIL("Unable to create reconnect options!"); 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 +}