diff --git a/buildfiles/Darwin.arm64.mak b/buildfiles/Darwin.arm64.mak index d202f20..fefb47e 100644 --- a/buildfiles/Darwin.arm64.mak +++ b/buildfiles/Darwin.arm64.mak @@ -32,7 +32,7 @@ UTIL_PROGS=das1_inctime das2_prtime das1_fxtime das2_ascii das2_bin_avg \ das2_cache_rdr das_node TEST_PROGS:=TestUnits TestArray TestVariable LoadStream TestBuilder \ - TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem + TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem TestCredMngr ifeq ($(SPICE),yes) TEST_PROGS:=$(TEST_PROGS) TestSpice @@ -185,6 +185,8 @@ test: $(BD) $(BD)/$(TARG) $(BUILD_TEST_PROGS) $(BULID_UTIL_PROGS) @$(BD)/TestCatalog @echo "INFO: Running unit test for dataset builder, $(BD)/TestBuilder..." @$(BD)/TestBuilder + @echo "INFO: Running unit test for credentials manager, $(BD)/TestCredMngr..." + @$(BD)/TestCredMngr $(BD) @echo "INFO: All test programs completed without errors" diff --git a/buildfiles/Darwin.mak b/buildfiles/Darwin.mak index 8c93142..f67f872 100644 --- a/buildfiles/Darwin.mak +++ b/buildfiles/Darwin.mak @@ -29,7 +29,7 @@ UTIL_PROGS=das1_inctime das2_prtime das1_fxtime das2_ascii das2_bin_avg \ das2_cache_rdr das_node TEST_PROGS:=TestUnits TestArray TestVariable LoadStream TestBuilder \ - TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem + TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem TestCredMngr ifeq ($(SPICE),yes) TEST_PROGS:=$(TEST_PROGS) TestSpice @@ -184,6 +184,8 @@ test: $(BD) $(BD)/$(TARG) $(BUILD_TEST_PROGS) $(BULID_UTIL_PROGS) @$(BD)/TestCatalog @echo "INFO: Running unit test for dataset builder, $(BD)/TestBuilder..." @$(BD)/TestBuilder + @echo "INFO: Running unit test for credentials manager, $(BD)/TestCredMngr..." + @$(BD)/TestCredMngr $(BD) @echo "INFO: All test programs completed without errors" diff --git a/buildfiles/Linux.mak b/buildfiles/Linux.mak index 9dcb21b..40e7c9e 100644 --- a/buildfiles/Linux.mak +++ b/buildfiles/Linux.mak @@ -30,7 +30,7 @@ UTIL_PROGS=das1_inctime das2_prtime das1_fxtime das2_ascii das2_bin_avg \ das2_cache_rdr das_node TEST_PROGS:=TestUnits TestArray TestVariable LoadStream TestBuilder \ - TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem + TestAuth TestCatalog TestTT2000 ex_das_cli ex_das_ephem TestCredMngr ifeq ($(SPICE),yes) TEST_PROGS:=$(TEST_PROGS) TestSpice @@ -186,6 +186,8 @@ test: $(BD) $(BD)/$(TARG).a $(BUILD_TEST_PROGS) $(BULID_UTIL_PROGS) @$(BD)/TestBuilder @echo "INFO: Running unit test for dataset loader, $(BD)/LoadStream..." @$(BD)/LoadStream + @echo "INFO: Running unit test for credentials manager, $(BD)/TestCredMngr..." + @$(BD)/TestCredMngr $(BD) @echo "INFO: All test programs completed without errors" test_spice:$(BD) $(BD)/$(TARG).a $(BUILD_TEST_PROGS) $(BULID_UTIL_PROGS) diff --git a/buildfiles/Windows.mak b/buildfiles/Windows.mak index cb5fd80..9395e53 100644 --- a/buildfiles/Windows.mak +++ b/buildfiles/Windows.mak @@ -73,7 +73,7 @@ UTIL_PROGS=$(BD)\das1_inctime.exe $(BD)\das2_prtime.exe $(BD)\das1_fxtime.exe \ TEST_PROGS=$(BD)\TestUnits.exe $(BD)\TestArray.exe $(BD)\LoadStream.exe \ $(BD)\TestBuilder.exe $(BD)\TestAuth.exe $(BD)\TestCatalog.exe $(BD)\TestTT2000.exe \ - $(BD)\TestVariable.exe + $(BD)\TestVariable.exe $(BD)\TestCredMngr.exe # Add in cspice error handling functions if SPICE = yes !if defined(SPICE) @@ -110,6 +110,7 @@ run_test: $(BD)\TestCatalog.exe $(BD)\TestBuilder.exe $(BD)\LoadStream.exe + $(BD)\TestCredMngr.exe $(BD) run_test_spice: run_test $(BD)\TestSpice.exe diff --git a/das2/credentials.c b/das2/credentials.c index f14348d..f232d97 100644 --- a/das2/credentials.c +++ b/das2/credentials.c @@ -184,13 +184,6 @@ bool das_cred_init( /* ************************************************************************** */ DasCredMngr* new_CredMngr(const char* sKeyStore) { - /* I don't actually have the code to read/write key files at this point */ - if(sKeyStore != NULL){ - das_error(DASERR_NOTIMP, "Reading/Writing to keystore files is not yet " - "implemented."); - return NULL; - } - DasCredMngr* pThis = (DasCredMngr*)calloc(1, sizeof(DasCredMngr)); das_credential fill; @@ -213,7 +206,7 @@ void del_CredMngr(DasCredMngr* pThis){ free(pThis); } -das_credential* _CredMngr_getCred( +das_credential* CredMngr_getCred( DasCredMngr* pThis, const char* sServer, const char* sRealm, const char* sDataset, bool bValidOnly ){ @@ -249,7 +242,7 @@ int CredMngr_addCred(DasCredMngr* pThis, const das_credential* pCred) /* fprintf(stderr, "Adding server: %s, realm: %s, dataset: %s, hash: %s", pCred->sServer, pCred->sRealm, pCred->sDataset, pCred->sHash); */ - pOld = _CredMngr_getCred(pThis, pCred->sServer, pCred->sRealm, pCred->sDataset, false); + pOld = CredMngr_getCred(pThis, pCred->sServer, pCred->sRealm, pCred->sDataset, false); if(pOld == NULL) DasAry_append(pThis->pCreds, (const byte*)pCred, 1); else @@ -273,7 +266,7 @@ int CredMngr_addUserPass( } /* Hash it */ - snprintf(sBuf, DASCRED_HASH_SZ+1, "%s:%s", sUser, sPassword); /* 257 is not an error */ + snprintf(sBuf, DASCRED_HASH_SZ+1, "%s:%s", sUser, sPass); /* 257 is not an error */ size_t uLen; char* sHash = das_b64_encode((unsigned char*)sBuf, strlen(sBuf), &uLen); /*fprintf(stderr, "DEBUG: Print hash: %s, length %zu\n", sHash, uLen); */ @@ -284,7 +277,7 @@ int CredMngr_addUserPass( return -1; } - if(! das_cred_init(sServer, sRealm, sDataset, sHash)) + if(! das_cred_init(&cred, sServer, sRealm, sDataset, sHash)) return -1; /* Function sets it's own error message */ return CredMngr_addCred(pThis, &cred); @@ -295,7 +288,7 @@ const char* CredMngr_getHttpAuth( DasCredMngr* pThis, const char* sServer, const char* sRealm, const char* sDataset ){ - das_credential* pCred = _CredMngr_getCred(pThis, sServer, sRealm, sDataset, true); + das_credential* pCred = CredMngr_getCred(pThis, sServer, sRealm, sDataset, true); if(pCred) return pCred->sHash; char sUser[128]; @@ -333,7 +326,7 @@ const char* CredMngr_getHttpAuth( /* Store it either in the old spot, or if that doesn't exist, make a * new one */ - pCred = _CredMngr_getCred(pThis, sServer, sRealm, sDataset, false); + pCred = CredMngr_getCred(pThis, sServer, sRealm, sDataset, false); if(pCred == NULL){ das_credential cred; memset(&cred, 0, sizeof(cred)); @@ -359,7 +352,7 @@ void CredMngr_authFailed( DasCredMngr* pThis, const char* sServer, const char* sRealm, const char* sDataset, const char* sMsg ){ - das_credential* pCred = _CredMngr_getCred(pThis, sServer, sRealm, sDataset, false); + das_credential* pCred = CredMngr_getCred(pThis, sServer, sRealm, sDataset, false); if(pCred != NULL) pCred->bValid = false; if(sMsg != NULL) @@ -471,7 +464,7 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) // Section begin and end are the same for empty sections aBeg[0] = aLine; - aEnd[4] = aLine + strlen(aLine) + 1; + aEnd[4] = aLine + strlen(aLine); iSection = 0; for(pChar = aLine; *pChar != '\0'; ++pChar){ if(*pChar == '|'){ @@ -497,10 +490,12 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) aBeg[iSection] += 1; } - pChar = aEnd[iSection]; - while((pChar >= aBeg[iSection]) && ((*pChar == ' ')||(*pChar == '\t'))){ + pChar = aEnd[iSection] - 1; + while((pChar >= aBeg[iSection]) && pChar > aBeg[iSection] && ( + (*pChar == ' ')||(*pChar == '\t')||(*pChar == '\n')||(*pChar == '\r') + )){ + *pChar = '\0'; --pChar; - *(aEnd[iSection]) = '\0'; aEnd[iSection] -= 1; } } @@ -509,8 +504,8 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) if((aBeg[0] == aEnd[0])||(aBeg[1] == aEnd[1])||(aBeg[4] == aEnd[4])) continue; - // Expect the key 'dataset' if aEnd[2] is not null - if((*(aEnd[2]) != '\0')&&(strcmp(aBeg[2], "dataset") != 0)){ + // Expect the key 'dataset' if for second string, if present + if((*(aBeg[2]) != '\0')&&(strcmp(aBeg[2], "dataset") != 0)){ daslog_warn_v( "%s,%d: Hashes for specific datasets must indicate the key 'dataset'", sIn, nLine @@ -518,7 +513,7 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) continue; } - if(das_cred_init( + if(!das_cred_init( &cred, aBeg[0], aBeg[1], *(aBeg[3]) == '\0' ? NULL : aBeg[3], aBeg[4] )){ daslog_warn_v("%s,%d: Could not parse credential", sIn, nLine); @@ -536,9 +531,9 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) das_credential* pNew = NULL; das_credential* pOld = NULL; for(ptrdiff_t i = 0; i < DasAry_size(pTmpCreds); ++i){ - pNew = (das_credential*)DasAry_getAt(pThis->pCreds, vtUnknown, IDX0(i)); + pNew = (das_credential*)DasAry_getAt(pTmpCreds, vtUnknown, IDX0(i)); - pOld = _CredMngr_getCred(pThis, pNew->sServer, pNew->sRealm, pNew->sDataset, false); + pOld = CredMngr_getCred(pThis, pNew->sServer, pNew->sRealm, pNew->sDataset, false); if(pOld == NULL){ DasAry_append(pThis->pCreds, (const byte*)pNew, 1); // append always copies } @@ -551,7 +546,13 @@ int CredMngr_load(DasCredMngr* pThis, const char* sSymKey, const char* sFile) } } + fclose(pIn); + dec_DasAry(pTmpCreds); // Frees the temporary credentials array + + // Save the new keystore location + snprintf(pThis->sKeyFile, DASCMGR_FILE_SZ - 1, "%s", sIn); + return nCreds; } diff --git a/das2/credentials.h b/das2/credentials.h index fdcf4ea..4418a9e 100644 --- a/das2/credentials.h +++ b/das2/credentials.h @@ -159,6 +159,25 @@ DAS_API void del_CredMngr(DasCredMngr* pThis); DAS_API int CredMngr_addCred(DasCredMngr* pThis, const das_credential* pCred); +/** Get direct memory access to a stored credential + * + * Used by other functions to find a credential for a particular URL + * + * @param pThis A credentials manager + * @param sServer The service end point (A URL without fragments or query params) + * @param sRealm The security realm + * @param sDataset If not NULL, the dataset parameter must equal this + * @param bValidOnly Only return valid credentials. Credentials are assmed valid + * unless + * + * @returns A pointer to the in-memory credential, NULL if no credential matched + * the given conditions + */ +DAS_API das_credential* CredMngr_getCred( + DasCredMngr* pThis, const char* sServer, const char* sRealm, + const char* sDataset, bool bValidOnly +); + /** Manually add a credential to a credentials manager instead of prompting the * user. * @@ -183,7 +202,10 @@ DAS_API int CredMngr_addUserPass( ); /** Retrieve an HTTP basic authentication token for a given dataset on a given - * server. + * server. + * + * Side Effect: + * This may call the .prompt() method, which may initiate Terminal IO. * * @param pThis A pointer to a credentials manager structure * @param sServer The name of the server for which these credentials apply diff --git a/das2/dataset.c b/das2/dataset.c index ae79788..24b98a3 100644 --- a/das2/dataset.c +++ b/das2/dataset.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "util.h" #include "dataset.h" diff --git a/test/TestCredMngr.c b/test/TestCredMngr.c new file mode 100644 index 0000000..3327987 --- /dev/null +++ b/test/TestCredMngr.c @@ -0,0 +1,138 @@ +/* Test saving and reading authentication keys */ + +/* Author: Chris Piker + * + * This file contains test and example code and is meant to explain an + * interface. + * + * As United States courts have ruled that interfaces cannot be copyrighted, + * the code in this individual source file, TestBuilder.c, is placed into the + * public domain and may be displayed, incorporated or otherwise re-used without + * restriction. It is offered to the public without any without any warranty + * including even the implied warranty of merchantability or fitness for a + * particular purpose. + */ + + #define _POSIX_C_SOURCE 200112L + +#include +#include + +#include + +#define PROG_ERR 64 + +/* ************************************************************************* */ + +int main(int argc, char** argv){ + + /* Exit on errors, log info messages and up, don't install a log handler */ + das_init(argv[0], DASERR_DIS_EXIT, 0, DASLOG_INFO, NULL); + + if(argc < 2){ + return das_error(PROG_ERR, "Working directory not provided on the command line"); + } + + char sFile[128] = {'\0'}; + snprintf(sFile, 127, +#ifndef _WIN32 + "%s/cred_test.txt", +#else + "%s\\cred_test.txt", +#endif + argv[1] + ); + + DasCredMngr* pMngr = new_CredMngr(sFile); + + const char* sEndPt1 = "https://rogers.place/das/server"; + const char* sRealm = "Neighborhood of Make-Believe"; + const char* sDataset = "Trolly/TrackCurrent"; + const char* sUser = "drjfever"; + const char* sPass = "really~4disco"; + + const char* sHashExpect = "ZHJqZmV2ZXI6cmVhbGx5fjRkaXNjbw=="; + CredMngr_addUserPass(pMngr, sEndPt1, sRealm, sDataset, sUser, sPass); + + + const char* sEndPt2 = "https://rogers.place/das/server/source/trolly/trackcurrent/flex"; + CredMngr_addUserPass(pMngr, sEndPt2, sRealm, NULL, sUser, sPass); + + CredMngr_save(pMngr, NULL, NULL); + del_CredMngr(pMngr); pMngr = NULL; + + pMngr = new_CredMngr(sFile); + + CredMngr_load(pMngr, NULL, NULL); + + /* Expect a vaild credential */ + const das_credential* pCred; + + if((pCred = CredMngr_getCred(pMngr,sEndPt2, sRealm, NULL, true))== NULL) + return das_error(PROG_ERR, "No matching credential found"); + + if(strcmp(pCred->sHash, sHashExpect) != 0){ + return das_error(PROG_ERR, "Credential hash mis-match"); + } + + if((pCred = CredMngr_getCred(pMngr, sEndPt1, sRealm, sDataset, true))== NULL) + return das_error(PROG_ERR, "No matching credential found"); + + if(strcmp(pCred->sHash, sHashExpect) != 0) + return das_error(PROG_ERR, "Credential hash mis-match"); + + + del_CredMngr(pMngr); pMngr = NULL; + + /* Try again with various odd credentials lines */ + snprintf(sFile, 127, +#ifndef _WIN32 + "%s/cred_test2.txt", +#else + "%s\\cred_test2.txt", +#endif + argv[1] + ); + + FILE* pOut = fopen(sFile, "wb"); + + fprintf(pOut, "# Some random text becasue we think this is a commentable file\n"); + fprintf(pOut, "# Now a totally bogus credential line\n"); + fprintf(pOut, "||||\n"); + fprintf(pOut, "# Something realistic, but wrong\n"); + fprintf(pOut, "\t\tsomeserver\t | some realm | | | | bad hash\n"); + fprintf(pOut, "# Something useful, but also wrong\n"); + fprintf(pOut, "https://a.bad.one | Casey's Place | ID | kitchen | d2Fua2E6d2Fua2E=\n"); + fprintf(pOut, "# News we can use\n"); + fprintf(pOut, "https://a.good.one:8080/test/server | \tCasey's Place\t | dataset | kitchen | d2Fua2E6d2Fua2E=\r\n"); + + fclose(pOut); + + pMngr = new_CredMngr(NULL); // Starts off with $HOME/.das2_auth + + // Switch the log level so that intentional errors don't show + int nOldLvl = daslog_setlevel(daslog_strlevel("error")); + + CredMngr_load(pMngr, NULL, sFile); // Switches to new location + + // Now switch it back + daslog_setlevel(nOldLvl); + + if(strcmp(pMngr->sKeyFile, sFile) != 0) + return das_error(PROG_ERR, "Failed to switch to new credentials location"); + + + if((pCred = CredMngr_getCred(pMngr, + "https://a.good.one:8080/test/server", "Casey's Place", "kitchen", true + ))== NULL) + return das_error(PROG_ERR, "No matching credential found"); + + if(strcmp(pCred->sHash, "d2Fua2E6d2Fua2E=") != 0) + return das_error(PROG_ERR, "Credential hash mis-match"); + + del_CredMngr(pMngr); + + daslog_info("All credentials handling tests passed."); + + return 0; +} \ No newline at end of file