Skip to content

Commit

Permalink
[ADDED] TLS: natsOptions_TLSHandshakeFirst() (#780)
Browse files Browse the repository at this point in the history
* [ADDED] TLS: natsOptions_TLSHandshakeFirst()

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 <ivan@synadia.com>

* Fixed flapper

If the cluster was not fully established when the rest of the test
was running, the library may get additional client URLs to reconnect
to (gossip protocol). Looks like this was happening more on Windows CI
than Linux.

Using some options to clamp all that.

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>

---------

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>
  • Loading branch information
kozlovic authored Aug 7, 2024
1 parent 613ec74 commit fbcddd1
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 5 deletions.
20 changes: 18 additions & 2 deletions src/conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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));
Expand Down
21 changes: 19 additions & 2 deletions src/nats.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/natsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ struct __natsOptions
bool pedantic;
bool allowReconnect;
bool secure;
bool tlsHandshakeFirst;
int ioBufSize;
int maxReconnect;
int64_t reconnectWait;
Expand Down
21 changes: 21 additions & 0 deletions src/opts.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions test/list_test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ _test(SSLBasic)
_test(SSLCertAndKeyFromMemory)
_test(SSLCiphers)
_test(SSLConnectVerboseOption)
_test(SSLHandshakeFirst)
_test(SSLLoadCAFromMemory)
_test(SSLMultithreads)
_test(SSLReconnectWithAuthError)
Expand Down
70 changes: 69 additions & 1 deletion test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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!");

Expand Down
16 changes: 16 additions & 0 deletions test/tlsfirst.conf
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit fbcddd1

Please sign in to comment.