mirror of
https://github.com/eclipse-mosquitto/mosquitto.git
synced 2026-09-26 12:27:28 +08:00
Refactor password code
This commit is contained in:
committed by
Roger Light
parent
7b21f596d4
commit
b6710f9364
@@ -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
|
||||
|
||||
@@ -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 $@
|
||||
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+395
-62
@@ -18,20 +18,16 @@ Contributors:
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#ifdef WITH_TLS
|
||||
# include <openssl/opensslv.h>
|
||||
# include <openssl/evp.h>
|
||||
# include <openssl/rand.h>
|
||||
# include <openssl/buffer.h>
|
||||
#endif
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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 <windows.h>
|
||||
#else
|
||||
# include <stdbool.h>
|
||||
# include <unistd.h>
|
||||
# include <termios.h>
|
||||
# include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_TLS
|
||||
int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_password, int new_iterations)
|
||||
#ifdef WITH_ARGON2
|
||||
# include <argon2.h>
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-6
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,7 +26,6 @@ OBJS = \
|
||||
default_acl.o \
|
||||
groups.o \
|
||||
grouplist.o \
|
||||
hash.o \
|
||||
kicklist.o \
|
||||
plugin.o \
|
||||
roles.o \
|
||||
|
||||
@@ -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; i<len; i++){
|
||||
rc |= ((char *)a)[i] ^ ((char *)b)[i];
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata)
|
||||
{
|
||||
struct mosquitto_evt_basic_auth *ed = event_data;
|
||||
struct dynsec__data *data = userdata;
|
||||
struct dynsec__client *client;
|
||||
unsigned char password_hash[64]; /* For SHA512 */
|
||||
const char *clientid;
|
||||
|
||||
UNUSED(event);
|
||||
@@ -72,14 +57,10 @@ int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata
|
||||
return MOSQ_ERR_AUTH;
|
||||
}
|
||||
}
|
||||
if(client->pw.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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 '<plugin_opt_config_file>.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);
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2020-2021 Roger Light <roger@atchoo.org>
|
||||
|
||||
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 <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
+13
-151
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user