diff --git a/CMakeLists.txt b/CMakeLists.txt index ed06df5..f248572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,6 +140,7 @@ add_executable(alltests test/test_syslog_logger.cc test/test_daemon.cc test/test_change_user.cc + test/test_userdb.cc ) target_include_directories(alltests PRIVATE diff --git a/src/passwd/main.c b/src/passwd/main.c index fda2171..f2313b4 100644 --- a/src/passwd/main.c +++ b/src/passwd/main.c @@ -173,7 +173,7 @@ static int add_user(struct args * args) } struct userdb * db = userdb_create(args->pepper); - userdb_load(db, args->file); + userdb_load_file(db, args->file); userdb_add(db, args->username, args->password); bool result = userdb_save(db, args->file); userdb_dispose(db); @@ -191,7 +191,7 @@ static int remove_user(struct args * args) } struct userdb * db = userdb_create(args->pepper); - userdb_load(db, args->file); + userdb_load_file(db, args->file); userdb_remove(db, args->username); bool result = userdb_save(db, args->file); userdb_dispose(db); @@ -216,7 +216,7 @@ static int check_password(struct args * args) } struct userdb * db = userdb_create(args->pepper); - userdb_load(db, args->file); + userdb_load_file(db, args->file); bool result = userdb_check(db, args->username, args->password); userdb_dispose(db); diff --git a/src/userdb/userdb.c b/src/userdb/userdb.c index 172742a..1eabdae 100644 --- a/src/userdb/userdb.c +++ b/src/userdb/userdb.c @@ -77,20 +77,17 @@ static char hex_char(unsigned char value) static char * to_hex(unsigned char const * value, size_t length) { char * result = malloc((2 * length) + 1); - if (NULL != result) + for (size_t i = 0, j = 0; i < length; i++, j+=2) { - for (size_t i = 0, j = 0; i < length; i++, j+=2) - { - unsigned char high = (value[i] >> 4) & 0x0f; - unsigned char low = value[i] & 0x0f; + unsigned char high = (value[i] >> 4) & 0x0f; + unsigned char low = value[i] & 0x0f; - result[j ] = hex_char(high); - result[j + 1] = hex_char(low); - } - - result[2 * length] = '\0'; + result[j ] = hex_char(high); + result[j + 1] = hex_char(low); } + result[2 * length] = '\0'; + return result; } @@ -120,21 +117,46 @@ static char * compute_hash( } char * result = NULL; + EVP_MD_CTX * context = EVP_MD_CTX_new(); + EVP_DigestInit_ex(context, digest, NULL); + EVP_DigestUpdate(context, password, strlen(password)); + EVP_DigestUpdate(context, salt, strlen(salt)); + EVP_DigestUpdate(context, db->pepper, strlen(db->pepper)); + unsigned int hash_size = EVP_MD_size(digest); unsigned char * hash = malloc(hash_size); + EVP_DigestFinal_ex(context, hash, &hash_size); + EVP_MD_CTX_free(context); - if (NULL != hash) + result = to_hex(hash, hash_size); + free(hash); + + return result; +} + +static bool userdb_load( + struct userdb * db, + json_t * container) +{ + bool result = false; + if (NULL != container) { - EVP_MD_CTX * context = EVP_MD_CTX_new(); - EVP_DigestInit_ex(context, digest, NULL); - EVP_DigestUpdate(context, password, strlen(password)); - EVP_DigestUpdate(context, salt, strlen(salt)); - EVP_DigestUpdate(context, db->pepper, strlen(db->pepper)); - EVP_DigestFinal_ex(context, hash, &hash_size); - EVP_MD_CTX_free(context); + json_t * meta = json_object_get(container, "meta"); + json_t * users = json_object_get(container, "users"); - result = to_hex(hash, hash_size); - free(hash); + if ((is_compatible(meta)) && (json_is_object(users))) { + json_t * hash_algorithm = json_object_get(meta, "hash_algorithm"); + free(db->hash_algorithm); + db->hash_algorithm = strdup(json_string_value(hash_algorithm)); + + json_decref(db->users); + json_incref(users); + db->users = users; + + result = true; + } + + json_decref(container); } return result; @@ -144,12 +166,9 @@ struct userdb * userdb_create( char const * pepper) { struct userdb * db = malloc(sizeof(struct userdb)); - if (NULL != db) - { - db->users = json_object(); - db->pepper = strdup(pepper); - db->hash_algorithm = strdup(USERDB_HASH_ALGORITHM); - } + db->users = json_object(); + db->pepper = strdup(pepper); + db->hash_algorithm = strdup(USERDB_HASH_ALGORITHM); return db; } @@ -184,36 +203,23 @@ bool userdb_save( return (0 == result); } - -bool userdb_load( +bool userdb_load_file( struct userdb * db, char const * filename) { - bool result = false; json_t * container = json_load_file(filename, 0, NULL); - if (NULL != container) - { - json_t * meta = json_object_get(container, "meta"); - json_t * users = json_object_get(container, "users"); - - if ((is_compatible(meta)) && (json_is_object(users))) { - json_t * hash_algorithm = json_object_get(meta, "hash_algorithm"); - free(db->hash_algorithm); - db->hash_algorithm = strdup(json_string_value(hash_algorithm)); - - json_decref(db->users); - json_incref(users); - db->users = users; - - result = true; - } - - json_decref(container); - } - - return result; + return userdb_load(db, container); } +bool userdb_load_string( + struct userdb * db, + char const * contents) +{ + json_t * container = json_loads(contents, 0, NULL); + return userdb_load(db, container); +} + + void userdb_add( struct userdb * db, char const * username, @@ -243,7 +249,7 @@ static char const * json_object_get_string( json_t * object, char const * key) { - char const * result = NULL; + char const * result = ""; json_t * string_holder = json_object_get(object, key); if (json_is_string(string_holder)) diff --git a/src/userdb/userdb.h b/src/userdb/userdb.h index cf133d0..211051e 100644 --- a/src/userdb/userdb.h +++ b/src/userdb/userdb.h @@ -21,10 +21,14 @@ extern bool userdb_save( struct userdb * db, char const * filename); -extern bool userdb_load( +extern bool userdb_load_file( struct userdb * db, char const * filename); +extern bool userdb_load_string( + struct userdb * db, + char const * contents); + extern void userdb_add( struct userdb * db, char const * username, diff --git a/src/webfused/auth/file_authenticator.c b/src/webfused/auth/file_authenticator.c index 371bbeb..31737fc 100644 --- a/src/webfused/auth/file_authenticator.c +++ b/src/webfused/auth/file_authenticator.c @@ -37,7 +37,7 @@ wfd_file_authenticator_authenticate( if ((NULL != username) && (NULL != password)) { struct userdb * db = userdb_create(""); - result = userdb_load(db, authenticator->filename); + result = userdb_load_file(db, authenticator->filename); if (result) { result = userdb_check(db, username, password); diff --git a/test/test_userdb.cc b/test/test_userdb.cc new file mode 100644 index 0000000..7b4a1d4 --- /dev/null +++ b/test/test_userdb.cc @@ -0,0 +1,407 @@ +#include "userdb/userdb.h" +#include + +TEST(userdb, load_file) +{ + userdb * db = userdb_create(""); + + bool success = userdb_load_file(db, "test_passwd.json"); + ASSERT_TRUE(success); + + ASSERT_TRUE(userdb_check(db, "bob", "secret")); + + userdb_dispose(db); +} + +TEST(userdb, load_file_failed_no_file) +{ + userdb * db = userdb_create(""); + + bool success = userdb_load_file(db, "non_existing.json"); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, save) +{ + { + userdb * db = userdb_create(""); + userdb_add(db, "bob", "secret"); + ASSERT_TRUE(userdb_save(db, "/tmp/webfused_test.json")); + userdb_dispose(db); + } + + { + userdb * db = userdb_create(""); + ASSERT_TRUE(userdb_load_file(db, "/tmp/webfused_test.json")); + ASSERT_TRUE(userdb_check(db, "bob", "secret")); + userdb_dispose(db); + } +} + + +TEST(userdb, add) +{ + userdb * db = userdb_create(""); + + userdb_add(db, "bob", "secret"); + ASSERT_TRUE(userdb_check(db, "bob", "secret")); + ASSERT_FALSE(userdb_check(db, "bob", "i_dont_know")); + ASSERT_FALSE(userdb_check(db, "anna", "secret")); + + userdb_dispose(db); +} + +TEST(userdb, remove) +{ + userdb * db = userdb_create(""); + ASSERT_NE(nullptr, db); + + userdb_add(db, "bob", "secret"); + ASSERT_TRUE(userdb_check(db, "bob", "secret")); + + userdb_remove(db, "bob"); + ASSERT_FALSE(userdb_check(db, "bob", "secret")); + + userdb_dispose(db); +} + +TEST(userdb, update) +{ + userdb * db = userdb_create(""); + ASSERT_NE(nullptr, db); + + userdb_add(db, "bob", "secret"); + ASSERT_TRUE(userdb_check(db, "bob", "secret")); + + userdb_add(db, "bob", "new_secret"); + ASSERT_FALSE(userdb_check(db, "bob", "secret")); + ASSERT_TRUE(userdb_check(db, "bob", "new_secret")); + + userdb_dispose(db); +} + +TEST(userdb, load_string) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_TRUE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_no_json) +{ + userdb * db = userdb_create(""); + char const contents[] = "brummni"; + + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_invalid_type) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"any-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_invalid_version) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 2," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_missing_type) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_type_not_string) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": 42," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_missing_major_version) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_major_version_not_int) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": false," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_missing_minor_version) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_minor_version_not_int) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": false," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_missing_hash_alg) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_hash_alg_not_string) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": 42" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_missing_meta) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, load_fail_unsupported_hash) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"brummni\"" + "}," + "\"users\": {" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_FALSE(success); + + userdb_dispose(db); +} + +TEST(userdb, fail_missing_user_salt) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + " \"bob\": {" + " \"password_hash\": \"e51e27ce47054feead3d83068d47f2a07307d4877ac67da668ef43e0e466fe8c7b66651af14fdb8d48c51592ef5afa0c63f874d20861c6b9ef8e6513bfcaa330\"" + " }" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_TRUE(success); + + ASSERT_FALSE(userdb_check(db, "bob", "secret")); + userdb_dispose(db); +} + +TEST(userdb, fail_missing_user_hash) +{ + userdb * db = userdb_create(""); + char const contents[] = + "{" + "\"meta\": {" + " \"type\": \"wf-userdb\"," + " \"major\": 1," + " \"minor\": 0," + " \"hash_algorithm\": \"sha512\"" + "}," + "\"users\": {" + " \"bob\": {" + " \"salt\": \"b3be6979921edecfea88c50d0d1ec40b7f8c383831b2276c65969ead18e47c03\"" + " }" + "}" + "}" + ; + bool success = userdb_load_string(db, contents); + ASSERT_TRUE(success); + + ASSERT_FALSE(userdb_check(db, "bob", "secret")); + userdb_dispose(db); +} \ No newline at end of file