diff --git a/src/conn.c b/src/conn.c index 524d1d36b..bb01cbbe8 100644 --- a/src/conn.c +++ b/src/conn.c @@ -921,7 +921,7 @@ _connectProto(natsConnection *nc, char **proto) if (opts->userJWTHandler != NULL) { char *errTxt = NULL; - bool userCb = opts->userJWTHandler != natsConn_userFromFile; + bool userCb = opts->userJWTHandler != natsConn_userCreds; // If callback is not the internal one, we need to release connection lock. if (userCb) @@ -4222,12 +4222,15 @@ _getJwtOrSeed(char **val, const char *fn, bool seed, int item) } natsStatus -natsConn_userFromFile(char **userJWT, char **customErrTxt, void *closure) +natsConn_userCreds(char **userJWT, char **customErrTxt, void *closure) { natsStatus s = NATS_OK; userCreds *uc = (userCreds*) closure; - s = _getJwtOrSeed(userJWT, uc->userOrChainedFile, false, 0); + if (uc->jwtAndSeedContent != NULL) + s = nats_GetJWTOrSeed(userJWT, uc->jwtAndSeedContent, 0); + else + s = _getJwtOrSeed(userJWT, uc->userOrChainedFile, false, 0); return NATS_UPDATE_ERR_STACK(s); } @@ -4238,7 +4241,9 @@ _sign(userCreds *uc, const unsigned char *input, int inputLen, unsigned char *si natsStatus s = NATS_OK; char *encodedSeed = NULL; - if (uc->seedFile != NULL) + if (uc->jwtAndSeedContent != NULL) + s = nats_GetJWTOrSeed(&encodedSeed, uc->jwtAndSeedContent, 1); + else if (uc->seedFile != NULL) s = _getJwtOrSeed(&encodedSeed, uc->seedFile, true, 0); else s = _getJwtOrSeed(&encodedSeed, uc->userOrChainedFile, true, 1); diff --git a/src/conn.h b/src/conn.h index b7eed8b06..001b0f4b0 100644 --- a/src/conn.h +++ b/src/conn.h @@ -126,7 +126,7 @@ natsStatus natsConn_publish(natsConnection *nc, natsMsg *msg, const char *reply, bool directFlush); natsStatus -natsConn_userFromFile(char **userJWT, char **customErrTxt, void *closure); +natsConn_userCreds(char **userJWT, char **customErrTxt, void *closure); natsStatus natsConn_signatureHandler(char **customErrTxt, unsigned char **sig, int *sigLen, const char *nonce, void *closure); diff --git a/src/nats.h b/src/nats.h index ca3bab9ec..79bceaaa9 100644 --- a/src/nats.h +++ b/src/nats.h @@ -2970,6 +2970,23 @@ natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, const char *userOrChainedFile, const char *seedFile); +/** \brief Sets JWT handler and handler to sign nonce that uses seed. + * + * This function acts similarly to natsOptions_SetUserCredentialsFromFiles but reads from memory instead + * from a file. Also it assumes that `memory` contains both the JWT and NKey seed. + * + * As for the format, @cf `natsOptions_SetUserCredentialsFromFiles` documentation. + * + * @see natsOptions_SetUserCredentialsFromFiles() + * + * @param opts the pointer to the #natsOptions object. + * @param memory string containing user JWT and user NKey seed + * +*/ +NATS_EXTERN natsStatus +natsOptions_SetUserCredentialsFromMemory(natsOptions *opts, + const char *jwtAndSeedContent); + /** \brief Sets the NKey public key and signature callback. * * Any time the library creates a TCP connection to the server, the server diff --git a/src/natsp.h b/src/natsp.h index fb6dde262..76a4b5d07 100644 --- a/src/natsp.h +++ b/src/natsp.h @@ -192,6 +192,7 @@ typedef struct __userCreds { char *userOrChainedFile; char *seedFile; + char *jwtAndSeedContent; } userCreds; @@ -281,7 +282,7 @@ struct __natsOptions bool retryOnFailedConnect; // Callback/closure used to get the user JWT. Will be set to - // internal natsConn_userFromFile function when userCreds != NULL. + // internal natsConn_userCreds function when userCreds != NULL. natsUserJWTHandler userJWTHandler; void *userJWTClosure; @@ -294,8 +295,9 @@ struct __natsOptions // to the server. char *nkey; - // If user has invoked natsOptions_SetUserCredentialsFromFiles, this - // will be set and points to userOrChainedFile and possibly seedFile. + // If user has invoked natsOptions_SetUserCredentialsFromFiles or + // natsOptions_SetUserCredentialsFromMemory, this will be set and points to + // userOrChainedFile, seedFile, or possibly directly contains the JWT+seed content. struct __userCreds *userCreds; // Reconnect jitter added to reconnect wait diff --git a/src/opts.c b/src/opts.c index e95d2c26c..f63872143 100644 --- a/src/opts.c +++ b/src/opts.c @@ -1108,11 +1108,12 @@ _freeUserCreds(userCreds *uc) NATS_FREE(uc->userOrChainedFile); NATS_FREE(uc->seedFile); + NATS_FREE(uc->jwtAndSeedContent); NATS_FREE(uc); } static natsStatus -_createUserCreds(userCreds **puc, bool onlySeedFile, const char *uocf, const char *sf) +_createUserCreds(userCreds **puc, const char *uocf, const char *sf, const char* jwtAndSeedContent) { natsStatus s = NATS_OK; userCreds *uc = NULL; @@ -1121,17 +1122,27 @@ _createUserCreds(userCreds **puc, bool onlySeedFile, const char *uocf, const cha if (uc == NULL) return nats_setDefaultError(NATS_NO_MEMORY); - if (!onlySeedFile) + // in case of content, we do not need to read the files anymore + if (jwtAndSeedContent != NULL) { - uc->userOrChainedFile = NATS_STRDUP(uocf); - if (uc->userOrChainedFile == NULL) + uc->jwtAndSeedContent = NATS_STRDUP(jwtAndSeedContent); + if (uc->jwtAndSeedContent == NULL) s = nats_setDefaultError(NATS_NO_MEMORY); } - if ((s == NATS_OK) && sf != NULL) + else { - uc->seedFile = NATS_STRDUP(sf); - if (uc->seedFile == NULL) - s = nats_setDefaultError(NATS_NO_MEMORY); + if (uocf) + { + uc->userOrChainedFile = NATS_STRDUP(uocf); + if (uc->userOrChainedFile == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + } + if ((s == NATS_OK) && sf != NULL) + { + uc->seedFile = NATS_STRDUP(sf); + if (uc->seedFile == NULL) + s = nats_setDefaultError(NATS_NO_MEMORY); + } } if (s != NATS_OK) _freeUserCreds(uc); @@ -1141,6 +1152,41 @@ _createUserCreds(userCreds **puc, bool onlySeedFile, const char *uocf, const cha return NATS_UPDATE_ERR_STACK(s); } +static void +_setAndUnlockOptsFromUserCreds(natsOptions *opts, userCreds *uc) +{ + // Free previous object + _freeUserCreds(opts->userCreds); + // Set to new one (possibly NULL) + opts->userCreds = uc; + + if (uc != NULL) + { + opts->userJWTHandler = natsConn_userCreds; + opts->userJWTClosure = (void*) uc; + + opts->sigHandler = natsConn_signatureHandler; + opts->sigClosure = (void*) uc; + + // NKey and UserCreds are mutually exclusive. + if (opts->nkey != NULL) + { + NATS_FREE(opts->nkey); + opts->nkey = NULL; + } + } + else + { + opts->userJWTHandler = NULL; + opts->userJWTClosure = NULL; + + opts->sigHandler = NULL; + opts->sigClosure = NULL; + } + + UNLOCK_OPTS(opts); +} + natsStatus natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, const char *userOrChainedFile, const char *seedFile) { @@ -1159,7 +1205,7 @@ natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, const char *userOrCha if (!nats_IsStringEmpty(userOrChainedFile)) { - s = _createUserCreds(&uc, false, userOrChainedFile, seedFile); + s = _createUserCreds(&uc, userOrChainedFile, seedFile, NULL); if (s != NATS_OK) { UNLOCK_OPTS(opts); @@ -1167,36 +1213,32 @@ natsOptions_SetUserCredentialsFromFiles(natsOptions *opts, const char *userOrCha } } - // Free previous object - _freeUserCreds(opts->userCreds); - // Set to new one (possibly NULL) - opts->userCreds = uc; + _setAndUnlockOptsFromUserCreds(opts, uc); - if (uc != NULL) - { - opts->userJWTHandler = natsConn_userFromFile; - opts->userJWTClosure = (void*) uc; + return NATS_OK; +} - opts->sigHandler = natsConn_signatureHandler; - opts->sigClosure = (void*) uc; +natsStatus +natsOptions_SetUserCredentialsFromMemory(natsOptions *opts, const char *jwtAndSeedContent) +{ + natsStatus s = NATS_OK; + userCreds *uc = NULL; - // NKey and UserCreds are mutually exclusive. - if (opts->nkey != NULL) + LOCK_AND_CHECK_OPTIONS(opts, 0); + + // if content is not NULL create user creds from it; + // otherwise NULL will later lead to setting handlers to NULL + if (jwtAndSeedContent != NULL) + { + s = _createUserCreds(&uc, NULL, NULL, jwtAndSeedContent); + if (s != NATS_OK) { - NATS_FREE(opts->nkey); - opts->nkey = NULL; + UNLOCK_OPTS(opts); + return NATS_UPDATE_ERR_STACK(s); } } - else - { - opts->userJWTHandler = NULL; - opts->userJWTClosure = NULL; - opts->sigHandler = NULL; - opts->sigClosure = NULL; - } - - UNLOCK_OPTS(opts); + _setAndUnlockOptsFromUserCreds(opts, uc); return NATS_OK; } @@ -1302,7 +1344,7 @@ natsOptions_SetNKeyFromSeed(natsOptions *opts, UNLOCK_OPTS(opts); return nats_setDefaultError(NATS_NO_MEMORY); } - s = _createUserCreds(&uc, true, NULL, seedFile); + s = _createUserCreds(&uc, NULL, seedFile, NULL); if (s != NATS_OK) { NATS_FREE(nk); @@ -1527,9 +1569,17 @@ natsOptions_clone(natsOptions *opts) } else if ((s == NATS_OK) && (opts->userCreds != NULL)) { - s = natsOptions_SetUserCredentialsFromFiles(cloned, - opts->userCreds->userOrChainedFile, - opts->userCreds->seedFile); + if (opts->userCreds->jwtAndSeedContent != NULL) + { + s = natsOptions_SetUserCredentialsFromMemory(cloned, + opts->userCreds->jwtAndSeedContent); + } + else + { + s = natsOptions_SetUserCredentialsFromFiles(cloned, + opts->userCreds->userOrChainedFile, + opts->userCreds->seedFile); + } } if ((s == NATS_OK) && (opts->inboxPfx != NULL)) s = _setCustomInboxPrefix(cloned, opts->inboxPfx, false); diff --git a/test/list.txt b/test/list.txt index ebc513c84..99cca19f8 100644 --- a/test/list.txt +++ b/test/list.txt @@ -167,6 +167,7 @@ GetRTT GetLocalIPAndPort UserCredsCallbacks UserCredsFromFiles +UserCredsFromMemory NKey NKeyFromSeed ConnSign diff --git a/test/test.c b/test/test.c index 58aa4bc1a..465e76692 100644 --- a/test/test.c +++ b/test/test.c @@ -2935,7 +2935,8 @@ test_natsOptions(void) && (opts->userCreds != NULL) && (strcmp(opts->userCreds->userOrChainedFile, "foo") == 0) && (strcmp(opts->userCreds->seedFile, "bar") == 0) - && (opts->userJWTHandler == natsConn_userFromFile) + && (opts->userCreds->jwtAndSeedContent == NULL) + && (opts->userJWTHandler == natsConn_userCreds) && (opts->userJWTClosure == (void*) opts->userCreds) && (opts->sigHandler == natsConn_signatureHandler) && (opts->sigClosure == (void*) opts->userCreds)); @@ -2949,6 +2950,28 @@ test_natsOptions(void) && (opts->sigHandler == NULL) && (opts->sigClosure == NULL)); + test("Set UserCredsFromMemory: "); + const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n"; + s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); + testCond((s == NATS_OK) + && (opts->userCreds != NULL) + && (opts->userCreds->userOrChainedFile == NULL) + && (opts->userCreds->seedFile == NULL) + && (strcmp(opts->userCreds->jwtAndSeedContent, jwtAndSeed) == 0) + && (opts->userJWTHandler == natsConn_userCreds) + && (opts->userJWTClosure == (void*) opts->userCreds) + && (opts->sigHandler == natsConn_signatureHandler) + && (opts->sigClosure == (void*) opts->userCreds)); + + test("Remove UserCredsFromMemory: "); + s = natsOptions_SetUserCredentialsFromMemory(opts, NULL); + testCond((s == NATS_OK) + && (opts->userCreds == NULL) + && (opts->userJWTHandler == NULL) + && (opts->userJWTClosure == NULL) + && (opts->sigHandler == NULL) + && (opts->sigClosure == NULL)); + test("Set NKey: "); s = natsOptions_SetNKey(opts, "pubkey", _dummySigCb, (void*) 1); testCond((s == NATS_OK) @@ -18925,6 +18948,115 @@ test_UserCredsCallbacks(void) _destroyDefaultThreadArgs(&arg); } +static void +test_UserCredsFromMemory(void) +{ + natsStatus s = NATS_OK; + natsConnection *nc = NULL; + natsOptions *opts = NULL; + natsOptions *opts2 = NULL; + natsPid pid = NATS_INVALID_PID; + natsThread *t = NULL; + struct threadArg arg; + + const char *jwtAndSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n\n-----BEGIN USER NKEY SEED-----\nSUAMK2FG4MI6UE3ACF3FK3OIQBCEIEZV7NSWFFEW63UXMRLFM2XLAXK4GY\n-----END USER NKEY SEED-----\n"; + const char *jwtWithoutSeed = "-----BEGIN NATS USER JWT----\nsome user jwt\n-----END NATS USER JWT-----\n"; + + s = natsOptions_Create(&opts); + if (s != NATS_OK) + FAIL("Unable to create options for test UserCredsFromFiles"); + + test("Clone: "); + s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); + if (s == NATS_OK) + { + opts2 = natsOptions_clone(opts); + if (opts2 == NULL) + s = NATS_NO_MEMORY; + } + IFOK(s, natsOptions_SetUserCredentialsFromMemory(opts, NULL)); + testCond((s == NATS_OK) + && (opts2->userCreds != NULL) + && (opts2->userJWTHandler == natsConn_userCreds) + && (opts2->userJWTClosure == (void*) opts2->userCreds) + && (opts2->sigHandler == natsConn_signatureHandler) + && (opts2->sigClosure == (void*) opts2->userCreds)); + natsOptions_Destroy(opts2); + + pid = _startServer("nats://127.0.0.1:4222", NULL, true); + CHECK_SERVER_STARTED(pid); + + test("invalidCreds provided: "); + s = natsOptions_SetUserCredentialsFromMemory(opts, "invalidCreds"); + IFOK(s, natsConnection_Connect(&nc, opts)); + testCond(s == NATS_NOT_FOUND); + + // Use a file that contains no seed + test("jwtAndSeed string has no seed: "); + s = natsOptions_SetUserCredentialsFromMemory(opts, jwtWithoutSeed); + IFOK(s, natsConnection_Connect(&nc, opts)); + testCond(s == NATS_NOT_FOUND); + + nc = NULL; + _stopServer(pid); + + // Start fake server that will send predefined "nonce" so we can check + // that connection is sending appropriate jwt and signature. + s = _createDefaultThreadArgsForCbTests(&arg); + if (s == NATS_OK) + { + // Set this to error, the mock server should set it to OK + // if it can start successfully. + arg.done = false; + arg.status = NATS_ERR; + arg.checkInfoCB = _checkJWTAndSigCB; + arg.string = "INFO {\"server_id\":\"22\",\"version\":\"latest\",\"go\":\"latest\",\"port\":4222,\"max_payload\":1048576,\"nonce\":\"nonce\"}\r\n"; + s = natsThread_Create(&t, _startMockupServerThread, (void*) &arg); + } + if (s == NATS_OK) + { + // Wait for server to be ready + natsMutex_Lock(arg.m); + while ((s != NATS_TIMEOUT) && (arg.status != NATS_OK)) + s = natsCondition_TimedWait(arg.c, arg.m, 2000); + natsMutex_Unlock(arg.m); + } + if (s != NATS_OK) + { + if (t != NULL) + { + natsThread_Join(t); + natsThread_Destroy(t); + } + natsOptions_Destroy(opts); + _destroyDefaultThreadArgs(&arg); + FAIL("Unable to setup test"); + } + + s = NATS_OK; + test("Connect with jwtAndSeed string: "); + s = natsOptions_SetUserCredentialsFromMemory(opts, jwtAndSeed); + IFOK(s, natsConnection_Connect(&nc, opts)); + testCond(s == NATS_OK); + + // Notify mock server we are done + natsMutex_Lock(arg.m); + arg.done = true; + natsCondition_Signal(arg.c); + natsMutex_Unlock(arg.m); + + natsConnection_Destroy(nc); + nc = NULL; + + natsThread_Join(t); + natsThread_Destroy(t); + t = NULL; + + _destroyDefaultThreadArgs(&arg); + + natsOptions_Destroy(opts); +} + static void test_UserCredsFromFiles(void) { @@ -19033,7 +19165,7 @@ test_UserCredsFromFiles(void) IFOK(s, natsOptions_SetUserCredentialsFromFiles(opts, NULL, NULL)); testCond((s == NATS_OK) && (opts2->userCreds != NULL) - && (opts2->userJWTHandler == natsConn_userFromFile) + && (opts2->userJWTHandler == natsConn_userCreds) && (opts2->userJWTClosure == (void*) opts2->userCreds) && (opts2->sigHandler == natsConn_signatureHandler) && (opts2->sigClosure == (void*) opts2->userCreds)); @@ -19219,7 +19351,7 @@ test_NKey(void) s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", "bar"); testCond((s == NATS_OK) && (opts->nkey == NULL) - && (opts->userJWTHandler == natsConn_userFromFile) + && (opts->userJWTHandler == natsConn_userCreds) && (opts->userJWTClosure == (void*) opts->userCreds) && (opts->sigHandler == natsConn_signatureHandler) && (opts->sigClosure == (void*) opts->userCreds)); @@ -19358,7 +19490,7 @@ test_NKeyFromSeed(void) s = natsOptions_SetUserCredentialsFromFiles(opts, "foo", NULL); testCond((s == NATS_OK) && (opts->nkey == NULL) - && (opts->userJWTHandler == natsConn_userFromFile) + && (opts->userJWTHandler == natsConn_userCreds) && (opts->userJWTClosure == (void*) opts->userCreds) && (opts->sigHandler == natsConn_signatureHandler) && (opts->sigClosure == (void*) opts->userCreds) @@ -34137,6 +34269,7 @@ static testInfo allTests[] = {"GetLocalIPAndPort", test_GetLocalIPAndPort}, {"UserCredsCallbacks", test_UserCredsCallbacks}, {"UserCredsFromFiles", test_UserCredsFromFiles}, + {"UserCredsFromMemory", test_UserCredsFromMemory}, {"NKey", test_NKey}, {"NKeyFromSeed", test_NKeyFromSeed}, {"ConnSign", test_ConnSign},