diff --git a/CMakeLists.txt b/CMakeLists.txt index a945e17..f818608 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,6 +157,24 @@ install(FILES "${PROJECT_BINARY_DIR}/libwsfs-provider.pc" DESTINATION lib${LIB_S if(NOT WITHOUT_EXAMPLE) +# libuserdb + +add_library(userdb STATIC + example/lib/userdb/src/userdb.c +) + +target_include_directories(userdb PUBLIC + example/lib/userdb/include + ${OPENSSL_INCLUDE_DIRS} + ${JANSSON_INCLUDE_DIRS} +) + +target_compile_options(userdb PUBLIC + ${C_WARNINGS} + ${OPENSSL_CFLAGS_OTHER} + ${JANSSON_CFLAGS_OTHER} +) + # daemon add_executable(wsfsd @@ -189,12 +207,14 @@ add_executable(wsfs-passwd ) target_link_libraries(wsfs-passwd PUBLIC + userdb ${OPENSSL_LIBRARIES} ${JANSSON_LIBRARIES} ) target_include_directories(wsfs-passwd PUBLIC example/passwd + example/lib/userdb/include ${OPENSSL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ) diff --git a/example/lib/userdb/include/userdb.h b/example/lib/userdb/include/userdb.h new file mode 100644 index 0000000..cf133d0 --- /dev/null +++ b/example/lib/userdb/include/userdb.h @@ -0,0 +1,46 @@ +#ifndef USERDB_H +#define USERDB_H + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct userdb; + +extern struct userdb * userdb_create( + char const * pepper); + +extern void userdb_dispose(struct userdb * db); + +extern bool userdb_save( + struct userdb * db, + char const * filename); + +extern bool userdb_load( + struct userdb * db, + char const * filename); + +extern void userdb_add( + struct userdb * db, + char const * username, + char const * password); + +extern void userdb_remove( + struct userdb * db, + char const * user); + +extern bool userdb_check( + struct userdb * db, + char const * username, + char const * password); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/example/lib/userdb/src/userdb.c b/example/lib/userdb/src/userdb.c new file mode 100644 index 0000000..e8493e0 --- /dev/null +++ b/example/lib/userdb/src/userdb.c @@ -0,0 +1,278 @@ +#include "userdb.h" + +#include +#include +#include +#include + +#include +#include + +#define USERDB_HASH_ALGORITHM "sha512" +#define USERDB_MAJOR 1 +#define USERDB_MINOR 0 + +#define USERDB_SALT_SIZE 32 + +struct userdb +{ + json_t * users; + char * pepper; + char * hash_algorithm; +}; + +static bool is_compatible(json_t * meta) +{ + bool result = false; + if (json_is_object(meta)) + { + json_t * type = json_object_get(meta, "type"); + json_t * major = json_object_get(meta, "major"); + json_t * minor = json_object_get(meta, "minor"); + json_t * hash_algorithm = json_object_get(meta, "hash_algorithm"); + + result = ( + json_is_string(type) && + (0 == strcmp(json_string_value(type), "wsfs-userdb")) && + json_is_integer(major) && + (USERDB_MAJOR == json_integer_value(major)) && + json_is_integer(minor) && + json_is_string(hash_algorithm) + ); + + if (result) + { + char const * algorithm = json_string_value(hash_algorithm); + result = (NULL != EVP_get_digestbyname(algorithm)); + } + + } + return result; +} + +static char hex_char(unsigned char value) +{ + switch (value) + { + case 0x00: return '0'; + case 0x01: return '1'; + case 0x02: return '2'; + case 0x03: return '3'; + case 0x04: return '4'; + case 0x05: return '5'; + case 0x06: return '6'; + case 0x07: return '7'; + case 0x08: return '8'; + case 0x09: return '9'; + case 0x0a: return 'a'; + case 0x0b: return 'b'; + case 0x0c: return 'c'; + case 0x0d: return 'd'; + case 0x0e: return 'e'; + case 0x0f: return 'f'; + default: return '?'; + } +} + +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) + { + 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'; + } + + return result; +} + +static char * generate_salt(void) +{ + unsigned char buffer[USERDB_SALT_SIZE]; + int rc = RAND_bytes(buffer, USERDB_SALT_SIZE); + if (1 != rc) + { + fprintf(stderr, "fatal: failed to generate salt (OpenSSL RAND_bytes failed)\n"); + exit(EXIT_FAILURE); + } + + return to_hex(buffer, USERDB_SALT_SIZE); +} + +static char * compute_hash( + struct userdb * db, + char const * password, + char const * salt) +{ + EVP_MD const * digest = EVP_get_digestbyname(db->hash_algorithm); + if (NULL == digest) + { + fprintf(stderr, "error: hash algorithm %s not supported\n", db->hash_algorithm); + return NULL; + } + + char * result = NULL; + unsigned int hash_size = digest->md_size; + unsigned char * hash = malloc(hash_size); + + if (NULL != hash) + { + EVP_MD_CTX context; + EVP_MD_CTX_init(&context); + 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_cleanup(&context); + + result = to_hex(hash, hash_size); + free(hash); + } + + return result; +} + +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); + } + + return db; +} + +void userdb_dispose( + struct userdb * db) +{ + json_decref(db->users); + free(db->pepper); + free(db->hash_algorithm); + free(db); +} + +bool userdb_save( + struct userdb * db, + char const * filename) +{ + json_t * container = json_object(); + + json_t * meta = json_object(); + json_object_set_new(meta, "type", json_string("wsfs-userdb")); + json_object_set_new(meta, "major", json_integer(USERDB_MAJOR)); + json_object_set_new(meta, "minor", json_integer(USERDB_MINOR)); + json_object_set_new(meta, "hash_algorithm", json_string(db->hash_algorithm)); + json_object_set_new(container, "meta", meta); + + json_object_set(container, "users", db->users); + + int result = json_dump_file(container, filename, JSON_INDENT(2)); + json_decref(container); + + return (0 == result); +} + + +bool userdb_load( + 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; +} + +void userdb_add( + struct userdb * db, + char const * username, + char const * password) +{ + char * salt = generate_salt(); + char * hash = compute_hash(db, password, salt); + + json_t * user = json_object(); + json_object_set_new(user, "password_hash", json_string(hash)); + json_object_set_new(user, "salt", json_string(salt)); + + json_object_set_new(db->users, username, user); + + free(salt); + free(hash); +} + +void userdb_remove( + struct userdb * db, + char const * user) +{ + json_object_del(db->users, user); +} + +static char const * json_object_get_string( + json_t * object, + char const * key) +{ + char const * result = NULL; + + json_t * string_holder = json_object_get(object, key); + if (json_is_string(string_holder)) + { + result = json_string_value(string_holder); + } + + return result; +} + +bool userdb_check( + struct userdb * db, + char const * username, + char const * password) +{ + bool result = false; + + json_t * user = json_object_get(db->users, username); + if (json_is_object(user)) + { + char const * salt = json_object_get_string(user, "salt"); + char const * hash = json_object_get_string(user, "password_hash"); + + char * computed_hash = compute_hash(db, password, salt); + + result = (0 == strcmp(computed_hash, hash)); + free(computed_hash); + } + + return result; +} \ No newline at end of file diff --git a/example/passwd/main.c b/example/passwd/main.c index 3650c6f..c662000 100644 --- a/example/passwd/main.c +++ b/example/passwd/main.c @@ -11,10 +11,8 @@ #include #include #include +#include -#define HASH_ALGORITHM "sha512" -#define PASSWD_FORMAT_MAJOR 1 -#define PASSWD_FORMAT_MINOR 0 struct args { @@ -151,22 +149,11 @@ static void args_init(struct args * args) static int create_passwd(struct args * args) { - json_t * db = json_object(); + struct userdb * db = userdb_create(args->pepper); + bool result = userdb_save(db, args->file); + userdb_dispose(db); - json_t * meta = json_object(); - json_object_set_new(meta, "type", json_string("wsfs-passwd")); - json_object_set_new(meta, "major", json_integer(PASSWD_FORMAT_MAJOR)); - json_object_set_new(meta, "minor", json_integer(PASSWD_FORMAT_MINOR)); - json_object_set_new(meta, "hash_alorithm", json_string(HASH_ALGORITHM)); - json_object_set_new(db, "meta", meta); - - json_t * users = json_object(); - json_object_set_new(db, "users", users); - - int result = json_dump_file(db, args->file, JSON_INDENT(2)); - json_decref(db); - - return (0 == result) ? EXIT_SUCCESS : EXIT_FAILURE; + return (result) ? EXIT_SUCCESS : EXIT_FAILURE; } static int add_user(struct args * args) @@ -185,22 +172,56 @@ static int add_user(struct args * args) return EXIT_FAILURE; } - + struct userdb * db = userdb_create(args->pepper); + userdb_load(db, args->file); + userdb_add(db, args->username, args->password); + bool result = userdb_save(db, args->file); + userdb_dispose(db); - puts("add"); - return EXIT_FAILURE; + return (result) ? EXIT_SUCCESS : EXIT_FAILURE; } static int remove_user(struct args * args) { - puts("remove"); - return EXIT_FAILURE; + if (NULL == args->username) + { + fprintf(stderr, "error: missing username"); + args->show_help = true; + return EXIT_FAILURE; + } + + struct userdb * db = userdb_create(args->pepper); + userdb_load(db, args->file); + userdb_remove(db, args->username); + bool result = userdb_save(db, args->file); + userdb_dispose(db); + + return (result) ? EXIT_SUCCESS : EXIT_FAILURE; } static int check_password(struct args * args) { - puts("check"); - return EXIT_FAILURE; + if (NULL == args->username) + { + fprintf(stderr, "error: missing username"); + args->show_help = true; + return EXIT_FAILURE; + } + + if (NULL == args->password) + { + fprintf(stderr, "error: missing password"); + args->show_help = true; + return EXIT_FAILURE; + } + + struct userdb * db = userdb_create(args->pepper); + userdb_load(db, args->file); + bool result = userdb_check(db, args->username, args->password); + userdb_dispose(db); + + printf("%s\n", (result) ? "OK" : "FAILURE"); + return (result) ? EXIT_SUCCESS : EXIT_FAILURE; } static int invoke_invalid_command(struct args * args) @@ -246,84 +267,6 @@ static void args_cleanup(struct args * args) free(args->pepper); } -static char hex_char(unsigned char value) -{ - switch (value) - { - case 0x00: return '0'; - case 0x01: return '1'; - case 0x02: return '2'; - case 0x03: return '3'; - case 0x04: return '4'; - case 0x05: return '5'; - case 0x06: return '6'; - case 0x07: return '7'; - case 0x08: return '8'; - case 0x09: return '9'; - case 0x0a: return 'a'; - case 0x0b: return 'b'; - case 0x0c: return 'c'; - case 0x0d: return 'd'; - case 0x0e: return 'e'; - case 0x0f: return 'f'; - default: return '?'; - } -} - -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) - { - 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'; - } - - return result; -} - -static char * get_password_hash( - char const * password, - char const * salt, - char * pepper) -{ - EVP_MD const * digest = EVP_get_digestbyname(HASH_ALGORITHM); - if (NULL == digest) - { - fprintf(stderr, "error: hash algorithm %s not supported\n", HASH_ALGORITHM); - exit(EXIT_FAILURE); - } - - char * result = NULL; - unsigned int hash_size = digest->md_size; - unsigned char * hash = malloc(hash_size); - - if (NULL != hash) - { - EVP_MD_CTX context; - EVP_MD_CTX_init(&context); - EVP_DigestInit_ex(&context, digest, NULL); - EVP_DigestUpdate(&context, password, strlen(password)); - EVP_DigestUpdate(&context, salt, strlen(salt)); - EVP_DigestUpdate(&context, pepper, strlen(pepper)); - EVP_DigestFinal_ex(&context, hash, &hash_size); - EVP_MD_CTX_cleanup(&context); - - result = to_hex(hash, hash_size); - free(hash); - } - - return result; -} - static void openssl_cleanup(void) { FIPS_mode_set(0);