diff --git a/apps/mosquitto_ctrl/CMakeLists.txt b/apps/mosquitto_ctrl/CMakeLists.txt index 64205be3..ec15e3eb 100644 --- a/apps/mosquitto_ctrl/CMakeLists.txt +++ b/apps/mosquitto_ctrl/CMakeLists.txt @@ -5,7 +5,6 @@ if(WITH_TLS AND CJSON_FOUND) broker.c client.c dynsec.c - ../../plugins/dynamic-security/hash.c dynsec_client.c dynsec_group.c dynsec_role.c diff --git a/apps/mosquitto_ctrl/Makefile b/apps/mosquitto_ctrl/Makefile index de45964d..66345d1d 100644 --- a/apps/mosquitto_ctrl/Makefile +++ b/apps/mosquitto_ctrl/Makefile @@ -25,7 +25,6 @@ OBJS= \ OBJS_EXTERNAL= \ base64_mosq.o \ - dynsec_hash.o \ get_password.o \ json_help.o \ memory_mosq.o \ @@ -54,9 +53,6 @@ ${OBJS} : %.o: %.c mosquitto_ctrl.h base64_mosq.o : ${R}/common/base64_mosq.c ${R}/common/base64_mosq.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ -dynsec_hash.o : ${R}/plugins/dynamic-security/hash.c ${R}/plugins/dynamic-security/dynamic_security.h - ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -c $< -o $@ - example.o : example.c mosquitto_ctrl.h ${CROSS_COMPILE}${CC} $(LOCAL_CPPFLAGS) $(LOCAL_CFLAGS) -fPIC -c $< -o $@ diff --git a/apps/mosquitto_ctrl/dynsec.c b/apps/mosquitto_ctrl/dynsec.c index 8fe76cf9..00ce0357 100644 --- a/apps/mosquitto_ctrl/dynsec.c +++ b/apps/mosquitto_ctrl/dynsec.c @@ -564,47 +564,58 @@ static cJSON *init_add_client(const char *username, const char *password, const { cJSON *j_client, *j_roles, *j_role; struct mosquitto_pw pw; - char *salt64 = NULL, *hash64 = NULL; char buf[10]; memset(&pw, 0, sizeof(pw)); pw.hashtype = pw_sha512_pbkdf2; - if(pw__hash(password, &pw, true, PW_DEFAULT_ITERATIONS) != 0){ - return NULL; - } - if(base64__encode(pw.salt, (unsigned int)pw.salt_len, &salt64) - || base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64) - ){ - - fprintf(stderr, "dynsec init: Internal error while encoding password.\n"); - free(salt64); - free(hash64); + if(pw__create(&pw, password) != MOSQ_ERR_SUCCESS){ return NULL; } j_client = cJSON_CreateObject(); if(j_client == NULL){ - free(salt64); - free(hash64); return NULL; } snprintf(buf, sizeof(buf), "%d", PW_DEFAULT_ITERATIONS); if(cJSON_AddStringToObject(j_client, "username", username) == NULL || cJSON_AddStringToObject(j_client, "textName", "Dynsec admin user") == NULL - || cJSON_AddStringToObject(j_client, "password", hash64) == NULL - || cJSON_AddStringToObject(j_client, "salt", salt64) == NULL - || cJSON_AddRawToObject(j_client, "iterations", buf) == NULL ){ - free(salt64); - free(hash64); cJSON_Delete(j_client); return NULL; } - free(salt64); - free(hash64); + + if(pw.hashtype == pw_sha512_pbkdf2){ + char *salt_b64 = NULL, *password_b64 = NULL; + + if(base64__encode(pw.params.sha512_pbkdf2.salt, pw.params.sha512_pbkdf2.salt_len, &salt_b64) + || base64__encode(pw.params.sha512_pbkdf2.password_hash, sizeof(pw.params.sha512_pbkdf2.password_hash), &password_b64) + || cJSON_AddStringToObject(j_client, "salt", salt_b64) == NULL + || cJSON_AddStringToObject(j_client, "password", password_b64) == NULL + || cJSON_AddNumberToObject(j_client, "iterations", pw.params.sha512_pbkdf2.iterations) == NULL){ + + cJSON_Delete(j_client); + free(password_b64); + free(salt_b64); + return NULL; + } + free(password_b64); + free(salt_b64); + }else{ + if(pw__encode(&pw) != MOSQ_ERR_SUCCESS){ + cJSON_Delete(j_client); + return NULL; + } + + if(cJSON_AddStringToObject(j_client, "encoded_password", pw.encoded_password)){ + free(pw.encoded_password); + cJSON_Delete(j_client); + return NULL; + } + free(pw.encoded_password); + } j_roles = cJSON_CreateArray(); if(j_roles == NULL){ diff --git a/apps/mosquitto_ctrl/dynsec_client.c b/apps/mosquitto_ctrl/dynsec_client.c index 68486702..37f073ff 100644 --- a/apps/mosquitto_ctrl/dynsec_client.c +++ b/apps/mosquitto_ctrl/dynsec_client.c @@ -158,12 +158,9 @@ int dynsec_client__file_set_password(int argc, char *argv[], const char *file) FILE *fptr; char *fstr; cJSON *j_tree, *j_clients, *j_client; - cJSON *j_password = NULL, *j_salt = NULL, *j_iterations = NULL; struct dynsec__client client; - char *pw_buf = NULL, *salt_buf = NULL; char *json_str; int i; - int rc = MOSQ_ERR_UNKNOWN; memset(&client, 0, sizeof(client)); @@ -179,7 +176,7 @@ int dynsec_client__file_set_password(int argc, char *argv[], const char *file) fprintf(stderr, "Error: -i argument given, but no iterations provided.\n"); return MOSQ_ERR_INVAL; } - client.pw.iterations = atoi(argv[i+1]); + client.pw.params.sha512_pbkdf2.iterations = atoi(argv[i+1]); i++; }else{ fprintf(stderr, "Error: Unknown argument: %s\n", argv[i]); @@ -234,39 +231,51 @@ int dynsec_client__file_set_password(int argc, char *argv[], const char *file) const char *username_json; if(json_get_string(j_client, "username", &username_json, false) == MOSQ_ERR_SUCCESS){ if(!strcmp(username_json, username)){ - if(dynsec_auth__pw_hash(&client, password, client.pw.password_hash, sizeof(client.pw.password_hash), true) != MOSQ_ERR_SUCCESS){ + if(pw__create(&client.pw, password)){ fprintf(stderr, "Error: Problem generating password hash.\n"); - goto error; + return MOSQ_ERR_NOMEM; } - if(base64__encode(client.pw.password_hash, sizeof(client.pw.password_hash), &pw_buf) != MOSQ_ERR_SUCCESS){ - fprintf(stderr, "Error: Problem generating password hash.\n"); - rc = MOSQ_ERR_NOMEM; - goto error; - } - if(base64__encode(client.pw.salt, client.pw.salt_len, &salt_buf) != MOSQ_ERR_SUCCESS){ - fprintf(stderr, "Error: Problem generating password hash.\n"); - goto error; - } - if((j_password = cJSON_CreateString(pw_buf)) == NULL - || (j_salt = cJSON_CreateString(salt_buf)) == NULL - || (j_iterations = cJSON_CreateNumber(client.pw.iterations)) == NULL - ){ + if(client.pw.hashtype == pw_sha512_pbkdf2){ + char *password_b64, *salt_b64; + cJSON *j_password = NULL, *j_salt = NULL, *j_iterations = NULL; - fprintf(stderr, "Error: Out of memory.\n"); - rc = MOSQ_ERR_NOMEM; - goto error; + if(base64__encode(client.pw.params.sha512_pbkdf2.password_hash, sizeof(client.pw.params.sha512_pbkdf2.password_hash), &password_b64) != MOSQ_ERR_SUCCESS){ + fprintf(stderr, "Error: Problem generating password hash.\n"); + return MOSQ_ERR_NOMEM; + } + if(base64__encode(client.pw.params.sha512_pbkdf2.salt, client.pw.params.sha512_pbkdf2.salt_len, &salt_b64) != MOSQ_ERR_SUCCESS){ + free(password_b64); + fprintf(stderr, "Error: Problem generating password hash.\n"); + return MOSQ_ERR_NOMEM; + } + if((j_password = cJSON_CreateString(password_b64)) == NULL + || (j_salt = cJSON_CreateString(salt_b64)) == NULL + || (j_iterations = cJSON_CreateNumber(client.pw.params.sha512_pbkdf2.iterations)) == NULL + ){ + + free(password_b64); + free(salt_b64); + fprintf(stderr, "Error: Out of memory.\n"); + return MOSQ_ERR_NOMEM; + } + free(password_b64); + free(salt_b64); + + cJSON_ReplaceItemInObject(j_client, "password", j_password); + cJSON_ReplaceItemInObject(j_client, "salt", j_salt); + cJSON_ReplaceItemInObject(j_client, "iterations", j_iterations); + j_password = NULL; + j_salt = NULL; + j_iterations = NULL; + }else{ + if(pw__encode(&client.pw)){ + fprintf(stderr, "Error: Out of memory.\n"); + return MOSQ_ERR_NOMEM; + } + cJSON *j_encoded_password = cJSON_CreateString(client.pw.encoded_password); + cJSON_ReplaceItemInObject(j_client, "encoded_password", j_encoded_password); } - cJSON_ReplaceItemInObject(j_client, "password", j_password); - cJSON_ReplaceItemInObject(j_client, "salt", j_salt); - cJSON_ReplaceItemInObject(j_client, "iterations", j_iterations); - j_password = NULL; - j_salt = NULL; - j_iterations = NULL; - free(pw_buf); - pw_buf = NULL; - free(salt_buf); - salt_buf = NULL; json_str = cJSON_Print(j_tree); cJSON_Delete(j_tree); @@ -291,13 +300,6 @@ int dynsec_client__file_set_password(int argc, char *argv[], const char *file) fprintf(stderr, "Error: Client %s not found.\n", username); return MOSQ_ERR_SUCCESS; -error: - cJSON_Delete(j_password); - cJSON_Delete(j_salt); - cJSON_Delete(j_tree); - free(pw_buf); - free(salt_buf); - return rc; } int dynsec_client__set_password(int argc, char *argv[], cJSON *j_command) diff --git a/apps/mosquitto_passwd/mosquitto_passwd.c b/apps/mosquitto_passwd/mosquitto_passwd.c index 399bccaa..2ad8f8ea 100644 --- a/apps/mosquitto_passwd/mosquitto_passwd.c +++ b/apps/mosquitto_passwd/mosquitto_passwd.c @@ -29,6 +29,7 @@ Contributors: #include "get_password.h" #include "base64_mosq.h" +#include "memory_mosq.h" #include "password_mosq.h" #ifdef WIN32 @@ -131,7 +132,6 @@ static void print_usage(void) static int output_new_password(FILE *fptr, const char *username, const char *password, int iterations) { int rc; - char *salt64 = NULL, *hash64 = NULL; struct mosquitto_pw pw; if(password == NULL){ @@ -141,29 +141,24 @@ static int output_new_password(FILE *fptr, const char *username, const char *pas memset(&pw, 0, sizeof(pw)); pw.hashtype = hashtype; + if(hashtype == pw_sha512_pbkdf2){ + pw.params.sha512_pbkdf2.iterations = iterations; + } - rc = pw__hash(password, &pw, true, iterations); + rc = pw__create(&pw, password); if(rc){ fprintf(stderr, "Error: Unable to hash password.\n"); - }else{ - rc = base64__encode(pw.salt, pw.salt_len, &salt64); - if(rc){ - fprintf(stderr, "Error: Unable to encode salt.\n"); - }else{ - rc = base64__encode(pw.password_hash, sizeof(pw.password_hash), &hash64); - if(rc){ - fprintf(stderr, "Error: Unable to encode hash.\n"); - }else{ - if(pw.hashtype == pw_sha512_pbkdf2){ - fprintf(fptr, "%s:$%d$%d$%s$%s\n", username, hashtype, iterations, salt64, hash64); - }else{ - fprintf(fptr, "%s:$%d$%s$%s\n", username, hashtype, salt64, hash64); - } - } - } + return rc; } - free(salt64); - free(hash64); + + rc = pw__encode(&pw); + if(rc){ + fprintf(stderr, "Error: Unable to encode password.\n"); + return rc; + } + + fprintf(fptr, "%s:%s\n", username, pw.encoded_password); + mosquitto__free(pw.encoded_password); return rc; } diff --git a/common/password_mosq.c b/common/password_mosq.c index 2044252c..7cdc2a9c 100644 --- a/common/password_mosq.c +++ b/common/password_mosq.c @@ -18,20 +18,16 @@ Contributors: #include "config.h" -#include #ifdef WITH_TLS # include # include # include -# include #endif -#include -#include -#include #include #include "mosquitto.h" -#include "mosquitto_broker.h" +#include "base64_mosq.h" +#include "memory_mosq.h" #include "password_mosq.h" #ifdef WIN32 @@ -51,66 +47,25 @@ Contributors: # include #else # include -# include -# include -# include #endif -#ifdef WITH_TLS -int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_password, int new_iterations) +#ifdef WITH_ARGON2 +# include +#endif + +#ifdef WITH_ARGON2 +static int pw__hash_argon2id(const char *password, struct mosquitto_pw *pw) { - int rc; - unsigned int hash_len; - const EVP_MD *digest; - int iterations; -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX context; -#else - EVP_MD_CTX *context; -#endif - if(new_password){ - pw->salt_len = HASH_LEN; - rc = RAND_bytes(pw->salt, (int)pw->salt_len); - if(!rc){ - return MOSQ_ERR_UNKNOWN; - } - iterations = new_iterations; - }else{ - iterations = pw->iterations; - } - if(iterations < 1){ - return MOSQ_ERR_INVAL; - } - - digest = EVP_get_digestbyname("sha512"); - if(!digest){ - return MOSQ_ERR_UNKNOWN; - } - - if(pw->hashtype == pw_sha512){ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX_init(&context); - EVP_DigestInit_ex(&context, digest, NULL); - EVP_DigestUpdate(&context, password, strlen(password)); - EVP_DigestUpdate(&context, pw->salt, pw->salt_len); - EVP_DigestFinal_ex(&context, pw->password_hash, &hash_len); - EVP_MD_CTX_cleanup(&context); -#else - context = EVP_MD_CTX_new(); - EVP_DigestInit_ex(context, digest, NULL); - EVP_DigestUpdate(context, password, strlen(password)); - EVP_DigestUpdate(context, pw->salt, pw->salt_len); - EVP_DigestFinal_ex(context, pw->password_hash, &hash_len); - EVP_MD_CTX_free(context); -#endif - }else{ - pw->iterations = iterations; - hash_len = sizeof(pw->password_hash); - PKCS5_PBKDF2_HMAC(password, (int)strlen(password), - pw->salt, (int)pw->salt_len, iterations, - digest, (int)hash_len, pw->password_hash); - } +ARGON2_PUBLIC size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, + uint32_t parallelism, uint32_t saltlen, + uint32_t hashlen, argon2_type type); + char encoded[1024]; + int rc = argon2id_hash_encoded(1, 47104, 1, + password, strlen(password), + pw->params.argon2id.salt, pw->params.argon2id.salt_len, + HASH_LEN, + encoded, sizeof(encoded)); return MOSQ_ERR_SUCCESS; } @@ -133,3 +88,381 @@ int pw__memcmp_const(const void *a, const void *b, size_t len) return rc; #endif } + +/* ================================================== + * SHA512 PBKDF2 + * ================================================== */ +#ifdef WITH_TLS +static int pw__hash_sha512_pbkdf2(const char *password, struct mosquitto_pw *pw, unsigned char *password_hash, unsigned int hash_len, int iterations) +{ + const EVP_MD *digest; + + digest = EVP_get_digestbyname("sha512"); + if(!digest){ + return MOSQ_ERR_UNKNOWN; + } + + PKCS5_PBKDF2_HMAC(password, (int)strlen(password), + pw->params.sha512.salt, (int)pw->params.sha512.salt_len, iterations, + digest, (int)hash_len, password_hash); + + return MOSQ_ERR_SUCCESS; +} +#endif + +static int pw__create_sha512_pbkdf2(struct mosquitto_pw *pw, const char *password) +{ +#ifdef WITH_TLS + pw->hashtype = pw_sha512_pbkdf2; + pw->params.sha512_pbkdf2.salt_len = HASH_LEN; + int rc = RAND_bytes(pw->params.sha512_pbkdf2.salt, (int)pw->params.sha512_pbkdf2.salt_len); + if(!rc){ + return MOSQ_ERR_UNKNOWN; + } + + if(pw->params.sha512_pbkdf2.iterations == 0){ + pw->params.sha512_pbkdf2.iterations = PW_DEFAULT_ITERATIONS; + } + return pw__hash_sha512_pbkdf2(password, pw, + pw->params.sha512_pbkdf2.password_hash, + sizeof(pw->params.sha512_pbkdf2.password_hash), + pw->params.sha512_pbkdf2.iterations); +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + + +static int pw__verify_sha512_pbkdf2(struct mosquitto_pw *pw, const char *password) +{ +#ifdef WITH_TLS + int rc; + unsigned char password_hash[HASH_LEN]; + + rc = pw__hash_sha512_pbkdf2(password, pw, + password_hash, sizeof(password_hash), + pw->params.sha512_pbkdf2.iterations); + + if(rc != MOSQ_ERR_SUCCESS) return MOSQ_ERR_AUTH; + + if(!pw__memcmp_const(pw->params.sha512_pbkdf2.password_hash, password_hash, HASH_LEN)){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_AUTH; + } +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + +static int pw__encode_sha512_pbkdf2(struct mosquitto_pw *pw) +{ +#ifdef WITH_TLS + int rc; + char *salt64 = NULL, *hash64 = NULL; + + rc = base64__encode(pw->params.sha512_pbkdf2.salt, pw->params.sha512_pbkdf2.salt_len, &salt64); + if(rc){ + return MOSQ_ERR_UNKNOWN; + } + + rc = base64__encode(pw->params.sha512_pbkdf2.password_hash, sizeof(pw->params.sha512_pbkdf2.password_hash), &hash64); + if(rc){ + free(salt64); + return MOSQ_ERR_UNKNOWN; + } + + free(pw->encoded_password); + size_t len = strlen("$6$$") + strlen("1,000,000,000,000") + strlen(salt64) + strlen(hash64) + 1; + pw->encoded_password = calloc(1, len); + if(!pw->encoded_password) return MOSQ_ERR_NOMEM; + + snprintf(pw->encoded_password, len, "$%d$%d$%s$%s", pw->hashtype, pw->params.sha512_pbkdf2.iterations, salt64, hash64); + + free(salt64); + free(hash64); + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + +static int pw__decode_sha512_pbkdf2(struct mosquitto_pw *pw, const char *salt_password) +{ +#ifdef WITH_TLS + char *sp_heap, *saveptr = NULL; + char *iterations_s; + char *salt_b64, *password_b64; + unsigned char *salt, *password; + unsigned int salt_len, password_len; + int rc; + + sp_heap = strdup(salt_password); + if(!sp_heap) return MOSQ_ERR_NOMEM; + + iterations_s = strtok_r(sp_heap, "$", &saveptr); + if(iterations_s == NULL){ + return MOSQ_ERR_INVAL; + } + pw->params.sha512_pbkdf2.iterations = atoi(iterations_s); + if(pw->params.sha512_pbkdf2.iterations < 1){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + + salt_b64 = strtok_r(NULL, "$", &saveptr); + if(salt_b64 == NULL){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + + rc = base64__decode(salt_b64, &salt, &salt_len); + if(rc != MOSQ_ERR_SUCCESS || (salt_len != 12 && salt_len != HASH_LEN)){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + memcpy(pw->params.sha512_pbkdf2.salt, salt, salt_len); + free(salt); + pw->params.sha512_pbkdf2.salt_len = salt_len; + + password_b64 = strtok_r(NULL, "$", &saveptr); + if(password_b64 == NULL){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + + rc = base64__decode(password_b64, &password, &password_len); + free(sp_heap); + + if(rc != MOSQ_ERR_SUCCESS || password_len != HASH_LEN){ + return MOSQ_ERR_INVAL; + } + memcpy(pw->params.sha512_pbkdf2.password_hash, password, password_len); + free(password); + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + +/* ================================================== + * SHA512 + * ================================================== */ +#ifdef WITH_TLS +static int pw__hash_sha512(const char *password, struct mosquitto_pw *pw, unsigned char *password_hash, unsigned int hash_len) +{ + const EVP_MD *digest; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX context; +#else + EVP_MD_CTX *context; +#endif + + digest = EVP_get_digestbyname("sha512"); + if(!digest){ + return MOSQ_ERR_UNKNOWN; + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX_init(&context); + EVP_DigestInit_ex(&context, digest, NULL); + EVP_DigestUpdate(&context, password, strlen(password)); + EVP_DigestUpdate(&context, pw->params.sha512.salt, pw->params.sha512.salt_len); + EVP_DigestFinal_ex(&context, password_hash, &hash_len); + EVP_MD_CTX_cleanup(&context); +#else + context = EVP_MD_CTX_new(); + EVP_DigestInit_ex(context, digest, NULL); + EVP_DigestUpdate(context, password, strlen(password)); + EVP_DigestUpdate(context, pw->params.sha512.salt, pw->params.sha512.salt_len); + EVP_DigestFinal_ex(context, password_hash, &hash_len); + EVP_MD_CTX_free(context); +#endif + + return MOSQ_ERR_SUCCESS; +} +#endif + +static int pw__create_sha512(struct mosquitto_pw *pw, const char *password) +{ +#ifdef WITH_TLS + pw->hashtype = pw_sha512; + pw->params.sha512.salt_len = HASH_LEN; + int rc = RAND_bytes(pw->params.sha512.salt, (int)pw->params.sha512.salt_len); + if(!rc){ + return MOSQ_ERR_UNKNOWN; + } + + return pw__hash_sha512(password, pw, pw->params.sha512.password_hash, sizeof(pw->params.sha512.password_hash)); +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + + +static int pw__verify_sha512(struct mosquitto_pw *pw, const char *password) +{ +#ifdef WITH_TLS + int rc; + unsigned char password_hash[HASH_LEN]; + + rc = pw__hash_sha512(password, pw, password_hash, sizeof(password_hash)); + if(rc != MOSQ_ERR_SUCCESS) return MOSQ_ERR_AUTH; + + if(!pw__memcmp_const(pw->params.sha512.password_hash, password_hash, HASH_LEN)){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_AUTH; + } +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + +static int pw__encode_sha512(struct mosquitto_pw *pw) +{ +#ifdef WITH_TLS + int rc; + char *salt64 = NULL, *hash64 = NULL; + + rc = base64__encode(pw->params.sha512.salt, pw->params.sha512.salt_len, &salt64); + if(rc){ + return MOSQ_ERR_UNKNOWN; + } + + rc = base64__encode(pw->params.sha512.password_hash, sizeof(pw->params.sha512.password_hash), &hash64); + if(rc){ + return MOSQ_ERR_UNKNOWN; + } + + free(pw->encoded_password); + size_t len = strlen("$6$$") + strlen(salt64) + strlen(hash64) + 1; + pw->encoded_password = calloc(1, len); + if(!pw->encoded_password) return MOSQ_ERR_NOMEM; + + snprintf(pw->encoded_password, len, "$%d$%s$%s", pw->hashtype, salt64, hash64); + + free(salt64); + free(hash64); + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED +#endif +} + +static int pw__decode_sha512(struct mosquitto_pw *pw, const char *salt_password) +{ +#ifdef WITH_TLS + char *sp_heap, *saveptr = NULL; + char *salt_b64, *password_b64; + unsigned char *salt, *password; + unsigned int salt_len, password_len; + int rc; + + sp_heap = strdup(salt_password); + if(!sp_heap) return MOSQ_ERR_NOMEM; + + salt_b64 = strtok_r(sp_heap, "$", &saveptr); + if(salt_b64 == NULL){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + + rc = base64__decode(salt_b64, &salt, &salt_len); + if(rc != MOSQ_ERR_SUCCESS || (salt_len != 12 && salt_len != HASH_LEN)){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + memcpy(pw->params.sha512.salt, salt, salt_len); + free(salt); + pw->params.sha512.salt_len = salt_len; + + password_b64 = strtok_r(NULL, "$", &saveptr); + if(password_b64 == NULL){ + free(sp_heap); + return MOSQ_ERR_INVAL; + } + + rc = base64__decode(password_b64, &password, &password_len); + free(sp_heap); + + if(rc != MOSQ_ERR_SUCCESS || password_len != HASH_LEN){ + return MOSQ_ERR_INVAL; + } + memcpy(pw->params.sha512.password_hash, password, password_len); + free(password); + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + +/* ================================================== + * Public + * ================================================== */ + +int pw__create(struct mosquitto_pw *pw, const char *password) +{ + switch(pw->hashtype){ + case pw_sha512: + return pw__create_sha512(pw, password); + case pw_sha512_pbkdf2: + return pw__create_sha512_pbkdf2(pw, password); + case pw_argon2id: + return MOSQ_ERR_AUTH; + default: + return pw__create_sha512_pbkdf2(pw, password); + } + + return MOSQ_ERR_INVAL; +} + +int pw__verify(struct mosquitto_pw *pw, const char *password) +{ + switch(pw->hashtype){ + case pw_sha512: + return pw__verify_sha512(pw, password); + case pw_sha512_pbkdf2: + return pw__verify_sha512_pbkdf2(pw, password); + case pw_argon2id: + return MOSQ_ERR_AUTH; + } + + return MOSQ_ERR_AUTH; +} + +int pw__encode(struct mosquitto_pw *pw) +{ + switch(pw->hashtype){ + case pw_sha512: + return pw__encode_sha512(pw); + case pw_sha512_pbkdf2: + return pw__encode_sha512_pbkdf2(pw); + case pw_argon2id: + return MOSQ_ERR_AUTH; + } + + return MOSQ_ERR_AUTH; +} + +int pw__decode(struct mosquitto_pw *pw, const char *password) +{ + if(password[0] != '$'){ + return MOSQ_ERR_INVAL; + } + + if(password[1] == '6' && password[2] == '$'){ + pw->hashtype = pw_sha512; + return pw__decode_sha512(pw, &password[3]); + }else if(password[1] == '7' && password[2] == '$'){ + pw->hashtype = pw_sha512_pbkdf2; + return pw__decode_sha512_pbkdf2(pw, &password[3]); + }else{ + return MOSQ_ERR_INVAL; + } +} diff --git a/common/password_mosq.h b/common/password_mosq.h index 5da336e6..583680e4 100644 --- a/common/password_mosq.h +++ b/common/password_mosq.h @@ -31,20 +31,39 @@ Contributors: enum mosquitto_pwhash_type{ pw_sha512 = 6, pw_sha512_pbkdf2 = 7, + pw_argon2id = 8, }; #define PW_DEFAULT_ITERATIONS 101 struct mosquitto_pw{ - unsigned char password_hash[HASH_LEN]; /* For SHA512 */ - unsigned char salt[HASH_LEN]; - size_t salt_len; - int iterations; + union { + struct { + unsigned char password_hash[HASH_LEN]; /* For SHA512 */ + unsigned char salt[HASH_LEN]; + size_t salt_len; + } sha512; + struct { + unsigned char password_hash[HASH_LEN]; /* For SHA512 */ + unsigned char salt[HASH_LEN]; + size_t salt_len; + int iterations; + } sha512_pbkdf2; + struct { + unsigned char password_hash[HASH_LEN]; + unsigned char salt[HASH_LEN]; + size_t salt_len; + int iterations; + } argon2id; + } params; + char *encoded_password; enum mosquitto_pwhash_type hashtype; bool valid; }; -int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_password, int new_iterations); -int pw__memcmp_const(const void *ptr1, const void *b, size_t len); +int pw__create(struct mosquitto_pw *pw, const char *password); +int pw__encode(struct mosquitto_pw *pw); +int pw__decode(struct mosquitto_pw *pw, const char *password); +int pw__verify(struct mosquitto_pw *pw, const char *password); #endif diff --git a/plugins/dynamic-security/CMakeLists.txt b/plugins/dynamic-security/CMakeLists.txt index a3c510c1..e7cb2e14 100644 --- a/plugins/dynamic-security/CMakeLists.txt +++ b/plugins/dynamic-security/CMakeLists.txt @@ -25,7 +25,6 @@ if(CJSON_FOUND AND WITH_TLS) dynamic_security.h groups.c grouplist.c - hash.c ../../common/json_help.c ../../common/json_help.h kicklist.c ../../common/misc_mosq.c ../../common/misc_mosq.h diff --git a/plugins/dynamic-security/Makefile b/plugins/dynamic-security/Makefile index 8c450b4d..0c9e6ccd 100644 --- a/plugins/dynamic-security/Makefile +++ b/plugins/dynamic-security/Makefile @@ -26,7 +26,6 @@ OBJS = \ default_acl.o \ groups.o \ grouplist.o \ - hash.o \ kicklist.o \ plugin.o \ roles.o \ diff --git a/plugins/dynamic-security/auth.c b/plugins/dynamic-security/auth.c index 3f12bcb8..cf2d20be 100644 --- a/plugins/dynamic-security/auth.c +++ b/plugins/dynamic-security/auth.c @@ -34,26 +34,11 @@ Contributors: * # * ################################################################ */ -static int memcmp_const(const void *a, const void *b, size_t len) -{ - size_t i; - int rc = 0; - - if(!a || !b) return 1; - - for(i=0; ipw.valid && dynsec_auth__pw_hash(client, ed->password, password_hash, sizeof(password_hash), false) == MOSQ_ERR_SUCCESS){ - if(memcmp_const(client->pw.password_hash, password_hash, sizeof(password_hash)) == 0){ - return MOSQ_ERR_SUCCESS; - }else{ - return MOSQ_ERR_AUTH; - } + if(client->pw.valid && pw__verify(&client->pw, ed->password) == MOSQ_ERR_SUCCESS){ + return MOSQ_ERR_SUCCESS; }else{ - return MOSQ_ERR_PLUGIN_DEFER; + return MOSQ_ERR_AUTH; } }else{ return MOSQ_ERR_PLUGIN_DEFER; diff --git a/plugins/dynamic-security/clients.c b/plugins/dynamic-security/clients.c index 0f2bc2dc..aae35075 100644 --- a/plugins/dynamic-security/clients.c +++ b/plugins/dynamic-security/clients.c @@ -148,37 +148,50 @@ int dynsec_clients__config_load(struct dynsec__data *data, cJSON *tree) client->disabled = disabled; } - int iterations; - const char *salt; const char *password; - json_get_int(j_client, "iterations", &iterations, 0, true); - if(json_get_string(j_client, "salt", &salt, false) == MOSQ_ERR_SUCCESS - && json_get_string(j_client, "password", &password, false) == MOSQ_ERR_SUCCESS - && iterations > 0){ - - client->pw.iterations = iterations; - - if(base64__decode(salt, &buf, &buf_len) != MOSQ_ERR_SUCCESS - || buf_len > sizeof(client->pw.salt)){ - - mosquitto_free(client); - continue; + if(json_get_string(j_client, "encoded_password", &password, false) == MOSQ_ERR_SUCCESS){ + /* FIXME */ + if(pw__decode(&client->pw, password) == MOSQ_ERR_SUCCESS){ + client->pw.valid = true; + }else{ + client->pw.valid = false; } - memcpy(client->pw.salt, buf, (size_t)buf_len); - client->pw.salt_len = (size_t)buf_len; - - if(base64__decode(password, &buf, &buf_len) != MOSQ_ERR_SUCCESS - || buf_len != sizeof(client->pw.password_hash)){ - - mosquitto_free(buf); - mosquitto_free(client); - continue; - } - memcpy(client->pw.password_hash, buf, (size_t)buf_len); - mosquitto_free(buf); - client->pw.valid = true; }else{ - client->pw.valid = false; + /* sha512-pbkdf2 only */ + int iterations; + const char *salt; + json_get_int(j_client, "iterations", &iterations, 0, true); + if(json_get_string(j_client, "salt", &salt, false) == MOSQ_ERR_SUCCESS + && json_get_string(j_client, "password", &password, false) == MOSQ_ERR_SUCCESS + && iterations > 0){ + + client->pw.hashtype = pw_sha512_pbkdf2; + client->pw.params.sha512_pbkdf2.iterations = iterations; + + if(base64__decode(salt, &buf, &buf_len) != MOSQ_ERR_SUCCESS + || buf_len > sizeof(client->pw.params.sha512_pbkdf2.salt)){ + + mosquitto_free(buf); + mosquitto_free(client); + continue; + } + memcpy(client->pw.params.sha512_pbkdf2.salt, buf, (size_t)buf_len); + client->pw.params.sha512_pbkdf2.salt_len = (size_t)buf_len; + mosquitto_free(buf); + + if(base64__decode(password, &buf, &buf_len) != MOSQ_ERR_SUCCESS + || buf_len != sizeof(client->pw.params.sha512_pbkdf2.password_hash)){ + + mosquitto_free(buf); + mosquitto_free(client); + continue; + } + memcpy(client->pw.params.sha512_pbkdf2.password_hash, buf, (size_t)buf_len); + mosquitto_free(buf); + client->pw.valid = true; + }else{ + client->pw.valid = false; + } } /* Client id */ @@ -241,8 +254,7 @@ int dynsec_clients__config_load(struct dynsec__data *data, cJSON *tree) static int dynsec__config_add_clients(struct dynsec__data *data, cJSON *j_clients) { struct dynsec__client *client, *client_tmp; - cJSON *j_client, *j_roles, *jtmp; - char *buf; + cJSON *j_client, *j_roles; HASH_ITER(hh, data->clients, client, client_tmp){ j_client = cJSON_CreateObject(); @@ -266,25 +278,33 @@ static int dynsec__config_add_clients(struct dynsec__data *data, cJSON *j_client cJSON_AddItemToObject(j_client, "roles", j_roles); if(client->pw.valid){ - if(base64__encode(client->pw.password_hash, sizeof(client->pw.password_hash), &buf) != MOSQ_ERR_SUCCESS){ - return 1; - } - jtmp = cJSON_CreateString(buf); - mosquitto_free(buf); - if(jtmp == NULL) return 1; - cJSON_AddItemToObject(j_client, "password", jtmp); + if(client->pw.hashtype == pw_sha512_pbkdf2){ + char *buf; - if(base64__encode(client->pw.salt, client->pw.salt_len, &buf) != MOSQ_ERR_SUCCESS){ - return 1; - } + if(base64__encode(client->pw.params.sha512_pbkdf2.password_hash, sizeof(client->pw.params.sha512_pbkdf2.password_hash), &buf) != MOSQ_ERR_SUCCESS){ + return 1; + } + cJSON *jtmp = cJSON_CreateString(buf); + mosquitto_free(buf); + if(jtmp == NULL) return 1; + cJSON_AddItemToObject(j_client, "password", jtmp); - jtmp = cJSON_CreateString(buf); - mosquitto_free(buf); - if(jtmp == NULL) return 1; - cJSON_AddItemToObject(j_client, "salt", jtmp); + if(base64__encode(client->pw.params.sha512_pbkdf2.salt, client->pw.params.sha512_pbkdf2.salt_len, &buf) != MOSQ_ERR_SUCCESS){ + return 1; + } - if(cJSON_AddIntToObject(j_client, "iterations", client->pw.iterations) == NULL){ - return 1; + jtmp = cJSON_CreateString(buf); + mosquitto_free(buf); + if(jtmp == NULL) return 1; + cJSON_AddItemToObject(j_client, "salt", jtmp); + + if(cJSON_AddIntToObject(j_client, "iterations", client->pw.params.sha512_pbkdf2.iterations) == NULL){ + return 1; + } + }else{ + if(cJSON_AddStringToObject(j_client, "encoded_password", client->pw.encoded_password) == NULL){ + return 1; + } } } } @@ -389,7 +409,7 @@ int dynsec_clients__process_create(struct dynsec__data *data, struct mosquitto_c } if(password){ - if(dynsec_auth__pw_hash(client, password, client->pw.password_hash, sizeof(client->pw.password_hash), true)){ + if(pw__create(&client->pw, password)){ mosquitto_control_command_reply(cmd, "Internal error"); client__free_item(data, client); return MOSQ_ERR_NOMEM; @@ -630,7 +650,7 @@ int dynsec_clients__process_set_id(struct dynsec__data *data, struct mosquitto_c static int client__set_password(struct dynsec__client *client, const char *password) { - if(dynsec_auth__pw_hash(client, password, client->pw.password_hash, sizeof(client->pw.password_hash), true) == MOSQ_ERR_SUCCESS){ + if(pw__create(&client->pw, password) == MOSQ_ERR_SUCCESS){ client->pw.valid = true; return MOSQ_ERR_SUCCESS; diff --git a/plugins/dynamic-security/config_init.c b/plugins/dynamic-security/config_init.c index 16369331..d1f79174 100644 --- a/plugins/dynamic-security/config_init.c +++ b/plugins/dynamic-security/config_init.c @@ -113,7 +113,7 @@ static int get_password_from_init_file(struct dynsec__data *data, char **pw) * * The contents of the MOSQUITTO_DYNSEC_PASSWORD environment variable * * Randomly generated passwords for "admin", "user", stored in plain text at '.pw' */ -static int generate_password(struct dynsec__data *data, int iterations, char **password, char **password_hash, char **salt) +static int generate_password(struct dynsec__data *data, cJSON *j_client, char **password) { struct mosquitto_pw pw; int i; @@ -158,21 +158,48 @@ static int generate_password(struct dynsec__data *data, int iterations, char **p (*password)[20] = '\0'; } - if(pw__hash(*password, &pw, true, iterations) != MOSQ_ERR_SUCCESS){ + pw.hashtype = pw_sha512_pbkdf2; + pw.params.sha512_pbkdf2.iterations = PW_DEFAULT_ITERATIONS + 1; + + if(pw__create(&pw, *password) != MOSQ_ERR_SUCCESS){ free(*password); *password = NULL; return MOSQ_ERR_UNKNOWN; } - if(base64__encode(pw.salt, (unsigned int)pw.salt_len, salt) - || base64__encode(pw.password_hash, sizeof(pw.password_hash), password_hash) - ){ + if(pw.hashtype == pw_sha512_pbkdf2){ + char *salt_b64 = NULL, *password_b64 = NULL; - free(*password); - free(*password_hash); - free(*salt); - return MOSQ_ERR_NOMEM; + if(base64__encode(pw.params.sha512_pbkdf2.salt, pw.params.sha512_pbkdf2.salt_len, &salt_b64) + || base64__encode(pw.params.sha512_pbkdf2.password_hash, sizeof(pw.params.sha512_pbkdf2.password_hash), &password_b64) + || cJSON_AddStringToObject(j_client, "salt", salt_b64) == NULL + || cJSON_AddStringToObject(j_client, "password", password_b64) == NULL + || cJSON_AddNumberToObject(j_client, "iterations", pw.params.sha512_pbkdf2.iterations) == NULL){ + + free(password_b64); + free(salt_b64); + free(*password); + *password = NULL; + return MOSQ_ERR_UNKNOWN; + } + free(password_b64); + free(salt_b64); + }else{ + if(pw__encode(&pw) != MOSQ_ERR_SUCCESS){ + free(*password); + *password = NULL; + return MOSQ_ERR_UNKNOWN; + } + + if(cJSON_AddStringToObject(j_client, "encoded_password", pw.encoded_password)){ + free(pw.encoded_password); + free(*password); + *password = NULL; + return MOSQ_ERR_UNKNOWN; + } + free(pw.encoded_password); } + return MOSQ_ERR_SUCCESS; } @@ -198,34 +225,26 @@ static int client_add_admin(struct dynsec__data *data, FILE *pwfile, cJSON *j_cl { cJSON *j_client, *j_roles; char *password = NULL; - char *password_hash = NULL; - char *salt = NULL; - - if(generate_password(data, 10000, &password, &password_hash, &salt)){ - return MOSQ_ERR_UNKNOWN; - } j_client = cJSON_CreateObject(); if(j_client == NULL){ return MOSQ_ERR_NOMEM; } + if(generate_password(data, j_client, &password)){ + cJSON_Delete(j_client); + return MOSQ_ERR_UNKNOWN; + } cJSON_AddItemToArray(j_clients, j_client); if(cJSON_AddStringToObject(j_client, "username", "admin") == NULL || cJSON_AddStringToObject(j_client, "textname", "Admin user") == NULL - || cJSON_AddStringToObject(j_client, "password", password_hash) == NULL - || cJSON_AddStringToObject(j_client, "salt", salt) == NULL - || cJSON_AddNumberToObject(j_client, "iterations", 10000) == NULL || (j_roles = cJSON_AddArrayToObject(j_client, "roles")) == NULL ){ + cJSON_Delete(j_client); free(password); - free(password_hash); - free(salt); return MOSQ_ERR_NOMEM; } - free(password_hash); - free(salt); if(client_role_add(j_roles, "super-admin") || client_role_add(j_roles, "sys-observe") @@ -247,37 +266,30 @@ static int client_add_user(struct dynsec__data *data, FILE *pwfile, cJSON *j_cli { cJSON *j_client, *j_roles; char *password = NULL; - char *password_hash = NULL; - char *salt = NULL; if(data->init_mode != dpwim_random){ return MOSQ_ERR_SUCCESS; } - if(generate_password(data, 10000, &password, &password_hash, &salt)){ - return MOSQ_ERR_UNKNOWN; - } - j_client = cJSON_CreateObject(); if(j_client == NULL){ return MOSQ_ERR_NOMEM; } - cJSON_AddItemToArray(j_clients, j_client); + if(generate_password(data, j_client, &password)){ + cJSON_Delete(j_client); + return MOSQ_ERR_UNKNOWN; + } + if(cJSON_AddStringToObject(j_client, "username", "democlient") == NULL || cJSON_AddStringToObject(j_client, "textname", "Demonstration client with full read/write access to the '#' topic hierarchy.") == NULL - || cJSON_AddStringToObject(j_client, "password", password_hash) == NULL - || cJSON_AddStringToObject(j_client, "salt", salt) == NULL - || cJSON_AddNumberToObject(j_client, "iterations", 10000) == NULL || (j_roles = cJSON_AddArrayToObject(j_client, "roles")) == NULL ){ free(password); - free(password_hash); - free(salt); + cJSON_Delete(j_client); return MOSQ_ERR_NOMEM; } - free(password_hash); - free(salt); + cJSON_AddItemToArray(j_clients, j_client); if(client_role_add(j_roles, "client")){ free(password); diff --git a/plugins/dynamic-security/hash.c b/plugins/dynamic-security/hash.c deleted file mode 100644 index 258dd701..00000000 --- a/plugins/dynamic-security/hash.c +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright (c) 2020-2021 Roger Light - -All rights reserved. This program and the accompanying materials -are made available under the terms of the Eclipse Public License 2.0 -and Eclipse Distribution License v1.0 which accompany this distribution. - -The Eclipse Public License is available at - https://www.eclipse.org/legal/epl-2.0/ -and the Eclipse Distribution License is available at - http://www.eclipse.org/org/documents/edl-v10.php. - -SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause - -Contributors: - Roger Light - initial implementation and documentation. -*/ - -#include "config.h" - -#include -#include -#include -#include - -#include "dynamic_security.h" -#include "mosquitto.h" -#include "mosquitto_broker.h" - -/* ################################################################ - * # - * # Password functions - * # - * ################################################################ */ - -int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password) -{ - const EVP_MD *digest; - int iterations; - - if(new_password){ - client->pw.salt_len = HASH_LEN; - if(RAND_bytes(client->pw.salt, (int)client->pw.salt_len) != 1){ - return MOSQ_ERR_UNKNOWN; - } - if(client->pw.iterations > 0){ - iterations = client->pw.iterations; - }else{ - iterations = PW_DEFAULT_ITERATIONS; - } - }else{ - iterations = client->pw.iterations; - } - if(iterations < 1){ - return MOSQ_ERR_INVAL; - } - client->pw.iterations = iterations; - - digest = EVP_get_digestbyname("sha512"); - if(!digest){ - return MOSQ_ERR_UNKNOWN; - } - - return !PKCS5_PBKDF2_HMAC(password, (int)strlen(password), - client->pw.salt, (int)client->pw.salt_len, iterations, - digest, password_hash_len, password_hash); -} diff --git a/plugins/dynamic-security/test.sh b/plugins/dynamic-security/test.sh index b67321b7..84b25fc7 100755 --- a/plugins/dynamic-security/test.sh +++ b/plugins/dynamic-security/test.sh @@ -1,3 +1,4 @@ rm test.json export MOSQUITTO_DYNSEC_PASSWORD=passwordpass -../../src/mosquitto -c test.conf +export VG="valgrind --log-file=vglog" +${VG} ../../src/mosquitto -c test.conf diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 2922bee3..703dc5ce 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -428,15 +428,8 @@ struct mosquitto__client_msg{ struct mosquitto__unpwd{ UT_hash_handle hh; char *username; - char *password; char *clientid; -#ifdef WITH_TLS - unsigned char *salt; - unsigned int password_len; - unsigned int salt_len; - int iterations; -#endif - enum mosquitto_pwhash_type hashtype; + struct mosquitto_pw pw; }; struct mosquitto__acl{ diff --git a/src/security_default.c b/src/security_default.c index ca561583..87581ecb 100644 --- a/src/security_default.c +++ b/src/security_default.c @@ -35,9 +35,6 @@ static int unpwd__file_parse(struct mosquitto__unpwd **unpwd, const char *passwo static int acl__cleanup(bool reload); static int unpwd__cleanup(struct mosquitto__unpwd **unpwd, bool reload); static int psk__file_parse(struct mosquitto__unpwd **psk_id, const char *psk_file); -#ifdef WITH_TLS -static int pw__digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len, enum mosquitto_pwhash_type hashtype, int iterations); -#endif static int mosquitto_basic_auth_default(int event, void *event_data, void *userdata); static int mosquitto_acl_check_default(int event, void *event_data, void *userdata); @@ -782,8 +779,8 @@ static int pwfile__parse(const char *file, struct mosquitto__unpwd **root) continue; } - unpwd->password = mosquitto__strdup(password); - if(!unpwd->password){ + unpwd->pw.encoded_password = mosquitto__strdup(password); + if(!unpwd->pw.encoded_password){ fclose(pwfile); mosquitto__FREE(unpwd->username); mosquitto__FREE(unpwd); @@ -810,10 +807,7 @@ static int pwfile__parse(const char *file, struct mosquitto__unpwd **root) void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd *item) { mosquitto__FREE(item->username); - mosquitto__FREE(item->password); -#ifdef WITH_TLS - mosquitto__FREE(item->salt); -#endif + mosquitto__FREE(item->pw.encoded_password); HASH_DEL(*unpwd, item); mosquitto__FREE(item); } @@ -823,82 +817,19 @@ void unpwd__free_item(struct mosquitto__unpwd **unpwd, struct mosquitto__unpwd * static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd) { struct mosquitto__unpwd *u, *tmp = NULL; - char *token; - unsigned char *salt; - unsigned int salt_len; - unsigned char *password; - unsigned int password_len; int rc; - enum mosquitto_pwhash_type hashtype; HASH_ITER(hh, *unpwd, u, tmp){ /* Need to decode password into hashed data + salt. */ - if(u->password == NULL){ + if(u->pw.encoded_password == NULL){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Missing password hash for user %s, removing entry.", u->username); unpwd__free_item(unpwd, u); continue; } - token = strtok(u->password, "$"); - if(token == NULL){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - continue; - } - - if(!strcmp(token, "6")){ - hashtype = pw_sha512; - }else if(!strcmp(token, "7")){ - hashtype = pw_sha512_pbkdf2; - }else{ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash type for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - continue; - } - - if(hashtype == pw_sha512_pbkdf2){ - token = strtok(NULL, "$"); - if(token == NULL){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - continue; - } - u->iterations = atoi(token); - if(u->iterations < 1){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid hash iterations for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - continue; - } - } - - token = strtok(NULL, "$"); - if(token == NULL){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - continue; - } - rc = base64__decode(token, &salt, &salt_len); - if(rc == MOSQ_ERR_SUCCESS && (salt_len == 12 || salt_len == HASH_LEN)){ - u->salt = salt; - u->salt_len = salt_len; - token = strtok(NULL, "$"); - if(token){ - rc = base64__decode(token, &password, &password_len); - if(rc == MOSQ_ERR_SUCCESS && password_len == HASH_LEN){ - mosquitto__FREE(u->password); - u->password = (char *)password; - u->password_len = password_len; - u->hashtype = hashtype; - }else{ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - } - }else{ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s, removing entry.", u->username); - unpwd__free_item(unpwd, u); - } - }else{ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password salt for user %s, removing entry.", u->username); + rc = pw__decode(&u->pw, u->pw.encoded_password); + if(rc){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password/salt for user %s, removing entry.", u->username); unpwd__free_item(unpwd, u); } } @@ -940,11 +871,11 @@ static int psk__file_parse(struct mosquitto__unpwd **psk_id, const char *psk_fil HASH_ITER(hh, (*psk_id), u, tmp){ /* Check for hex only digits */ - if(!u->password){ + if(!u->pw.encoded_password){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Empty psk for identity \"%s\".", u->username); return MOSQ_ERR_INVAL; } - if(strspn(u->password, "0123456789abcdefABCDEF") < strlen(u->password)){ + if(strspn(u->pw.encoded_password, "0123456789abcdefABCDEF") < strlen(u->pw.encoded_password)){ log__printf(NULL, MOSQ_LOG_ERR, "Error: psk for identity \"%s\" contains non-hexadecimal characters.", u->username); return MOSQ_ERR_INVAL; } @@ -958,11 +889,6 @@ static int mosquitto_basic_auth_default(int event, void *event_data, void *userd struct mosquitto_evt_basic_auth *ed = event_data; struct mosquitto__unpwd *u; struct mosquitto__unpwd *unpwd_ref; -#ifdef WITH_TLS - unsigned char hash[EVP_MAX_MD_SIZE]; - unsigned int hash_len; - int rc; -#endif UNUSED(event); UNUSED(userdata); @@ -981,24 +907,9 @@ static int mosquitto_basic_auth_default(int event, void *event_data, void *userd HASH_FIND(hh, unpwd_ref, ed->client->username, strlen(ed->client->username), u); if(u){ - if(u->password){ + if(u->pw.encoded_password){ if(ed->client->password){ -#ifdef WITH_TLS - rc = pw__digest(ed->client->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype, u->iterations); - if(rc == MOSQ_ERR_SUCCESS){ - if(hash_len == u->password_len && !pw__memcmp_const(u->password, hash, hash_len)){ - return MOSQ_ERR_SUCCESS; - }else{ - return MOSQ_ERR_AUTH; - } - }else{ - return rc; - } -#else - if(!strcmp(u->password, ed->client->password)){ - return MOSQ_ERR_SUCCESS; - } -#endif + return pw__verify(&u->pw, ed->client->password); }else{ return MOSQ_ERR_AUTH; } @@ -1020,11 +931,8 @@ static int unpwd__cleanup(struct mosquitto__unpwd **root, bool reload) HASH_ITER(hh, *root, u, tmp){ HASH_DEL(*root, u); - mosquitto__FREE(u->password); + mosquitto__FREE(u->pw.encoded_password); mosquitto__FREE(u->username); -#ifdef WITH_TLS - mosquitto__FREE(u->salt); -#endif mosquitto__FREE(u); } @@ -1267,56 +1175,10 @@ int mosquitto_psk_key_get_default(struct mosquitto *context, const char *hint, c HASH_ITER(hh, psk_id_ref, u, tmp){ if(!strcmp(u->username, identity)){ - strncpy(key, u->password, (size_t)max_key_len); + strncpy(key, u->pw.encoded_password, (size_t)max_key_len); return MOSQ_ERR_SUCCESS; } } return MOSQ_ERR_AUTH; } - -#ifdef WITH_TLS -int pw__digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len, enum mosquitto_pwhash_type hashtype, int iterations) -{ - const EVP_MD *digest; -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX context; -#else - EVP_MD_CTX *context; -#endif - - digest = EVP_get_digestbyname("sha512"); - if(!digest){ - /* FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n"); */ - return 1; - } - - if(hashtype == pw_sha512){ -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX_init(&context); - EVP_DigestInit_ex(&context, digest, NULL); - EVP_DigestUpdate(&context, password, strlen(password)); - EVP_DigestUpdate(&context, salt, salt_len); - /* hash is assumed to be EVP_MAX_MD_SIZE bytes long. */ - EVP_DigestFinal_ex(&context, hash, hash_len); - EVP_MD_CTX_cleanup(&context); -#else - context = EVP_MD_CTX_new(); - EVP_DigestInit_ex(context, digest, NULL); - EVP_DigestUpdate(context, password, strlen(password)); - EVP_DigestUpdate(context, salt, salt_len); - /* hash is assumed to be EVP_MAX_MD_SIZE bytes long. */ - EVP_DigestFinal_ex(context, hash, hash_len); - EVP_MD_CTX_free(context); -#endif - }else{ - *hash_len = EVP_MAX_MD_SIZE; - PKCS5_PBKDF2_HMAC(password, (int)strlen(password), - salt, (int)salt_len, iterations, - digest, (int)(*hash_len), hash); - } - - return MOSQ_ERR_SUCCESS; -} - -#endif