Add support for pbkdf2 hash iterations.

This commit is contained in:
Roger A. Light 2020-10-21 12:26:31 +01:00
parent 899695261a
commit 1851a0e1b1
5 changed files with 104 additions and 57 deletions

View File

@ -57,6 +57,7 @@ struct cb_helper {
const char *line; const char *line;
const char *username; const char *username;
const char *password; const char *password;
int iterations;
bool found; bool found;
}; };
@ -117,7 +118,7 @@ void print_usage(void)
printf("\nSee https://mosquitto.org/ for more information.\n\n"); printf("\nSee https://mosquitto.org/ for more information.\n\n");
} }
int output_new_password(FILE *fptr, const char *username, const char *password) int output_new_password(FILE *fptr, const char *username, const char *password, int iterations)
{ {
int rc; int rc;
char *salt64 = NULL, *hash64 = NULL; char *salt64 = NULL, *hash64 = NULL;
@ -127,7 +128,7 @@ int output_new_password(FILE *fptr, const char *username, const char *password)
pw.hashtype = hashtype; pw.hashtype = hashtype;
if(pw__hash(password, &pw, true)){ if(pw__hash(password, &pw, true, iterations)){
fprintf(stderr, "Error: Unable to hash password.\n"); fprintf(stderr, "Error: Unable to hash password.\n");
return 1; return 1;
} }
@ -147,7 +148,11 @@ int output_new_password(FILE *fptr, const char *username, const char *password)
return 1; return 1;
} }
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); fprintf(fptr, "%s:$%d$%s$%s\n", username, hashtype, salt64, hash64);
}
free(salt64); free(salt64);
free(hash64); free(hash64);
@ -261,7 +266,7 @@ int delete_pwuser(FILE *fptr, FILE *ftmp, const char *username)
* ====================================================================== */ * ====================================================================== */
static int update_file_cb(FILE *fptr, FILE *ftmp, const char *username, const char *password, const char *line, struct cb_helper *helper) static int update_file_cb(FILE *fptr, FILE *ftmp, const char *username, const char *password, const char *line, struct cb_helper *helper)
{ {
return output_new_password(ftmp, username, password); return output_new_password(ftmp, username, password, helper->iterations);
} }
int update_file(FILE *fptr, FILE *ftmp) int update_file(FILE *fptr, FILE *ftmp)
@ -283,12 +288,12 @@ static int update_pwuser_cb(FILE *fptr, FILE *ftmp, const char *username, const
}else{ }else{
/* Write out a new line for our matching username */ /* Write out a new line for our matching username */
helper->found = true; helper->found = true;
rc = output_new_password(ftmp, username, helper->password); rc = output_new_password(ftmp, username, helper->password, helper->iterations);
} }
return rc; return rc;
} }
int update_pwuser(FILE *fptr, FILE *ftmp, const char *username, const char *password) int update_pwuser(FILE *fptr, FILE *ftmp, const char *username, const char *password, int iterations)
{ {
struct cb_helper helper; struct cb_helper helper;
int rc; int rc;
@ -296,12 +301,13 @@ int update_pwuser(FILE *fptr, FILE *ftmp, const char *username, const char *pass
memset(&helper, 0, sizeof(helper)); memset(&helper, 0, sizeof(helper));
helper.username = username; helper.username = username;
helper.password = password; helper.password = password;
helper.iterations = iterations;
rc = pwfile_iterate(fptr, ftmp, update_pwuser_cb, &helper); rc = pwfile_iterate(fptr, ftmp, update_pwuser_cb, &helper);
if(helper.found){ if(helper.found){
return rc; return rc;
}else{ }else{
return output_new_password(ftmp, username, password); return output_new_password(ftmp, username, password, iterations);
} }
} }
@ -466,6 +472,7 @@ int main(int argc, char *argv[])
bool do_update_file = false; bool do_update_file = false;
char *backup_file; char *backup_file;
int idx; int idx;
int iterations = PW_DEFAULT_ITERATIONS;
signal(SIGINT, handle_sigint); signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint); signal(SIGTERM, handle_sigint);
@ -505,6 +512,17 @@ int main(int argc, char *argv[])
create_new = true; create_new = true;
}else if(!strcmp(argv[idx], "-D")){ }else if(!strcmp(argv[idx], "-D")){
delete_user = true; delete_user = true;
}else if(!strcmp(argv[idx], "-I")){
if(idx+1 == argc){
fprintf(stderr, "Error: -I argument given but not enough other arguments.\n");
return 1;
}
iterations = atoi(argv[idx+1]);
idx++;
if(iterations < 1){
fprintf(stderr, "Error: Number of iterations must be > 0.\n");
return 1;
}
}else if(!strcmp(argv[idx], "-U")){ }else if(!strcmp(argv[idx], "-U")){
do_update_file = true; do_update_file = true;
}else{ }else{
@ -622,7 +640,7 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
free(password_file); free(password_file);
rc = output_new_password(fptr, username, password_cmd); rc = output_new_password(fptr, username, password_cmd, iterations);
fclose(fptr); fclose(fptr);
return rc; return rc;
}else{ }else{
@ -663,7 +681,7 @@ int main(int argc, char *argv[])
}else{ }else{
if(batch_mode){ if(batch_mode){
/* Update password for individual user */ /* Update password for individual user */
rc = update_pwuser(fptr, ftmp, username, password_cmd); rc = update_pwuser(fptr, ftmp, username, password_cmd, iterations);
}else{ }else{
rc = get_password(password, MAX_BUFFER_LEN); rc = get_password(password, MAX_BUFFER_LEN);
if(rc){ if(rc){
@ -674,7 +692,7 @@ int main(int argc, char *argv[])
return rc; return rc;
} }
/* Update password for individual user */ /* Update password for individual user */
rc = update_pwuser(fptr, ftmp, username, password); rc = update_pwuser(fptr, ftmp, username, password, iterations);
} }
} }
if(rc){ if(rc){

View File

@ -454,6 +454,7 @@ struct mosquitto__unpwd{
unsigned char *salt; unsigned char *salt;
unsigned int password_len; unsigned int password_len;
unsigned int salt_len; unsigned int salt_len;
int iterations;
#endif #endif
enum mosquitto_pwhash_type hashtype; enum mosquitto_pwhash_type hashtype;
}; };

View File

@ -133,22 +133,29 @@ int base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len)
int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_salt) int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_password, int new_iterations)
{ {
int rc; int rc;
unsigned int hash_len; unsigned int hash_len;
const EVP_MD *digest; const EVP_MD *digest;
int iterations;
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_MD_CTX context; EVP_MD_CTX context;
#else #else
EVP_MD_CTX *context; EVP_MD_CTX *context;
#endif #endif
if(new_salt){ if(new_password){
rc = RAND_bytes(pw->salt, sizeof(pw->salt)); rc = RAND_bytes(pw->salt, sizeof(pw->salt));
if(!rc){ if(!rc){
return MOSQ_ERR_UNKNOWN; return MOSQ_ERR_UNKNOWN;
} }
iterations = new_iterations;
}else{
iterations = pw->iterations;
}
if(iterations < 1){
return MOSQ_ERR_INVAL;
} }
digest = EVP_get_digestbyname("sha512"); digest = EVP_get_digestbyname("sha512");
@ -173,9 +180,10 @@ int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_salt)
EVP_MD_CTX_free(context); EVP_MD_CTX_free(context);
#endif #endif
}else{ }else{
pw->iterations = iterations;
hash_len = sizeof(pw->password_hash); hash_len = sizeof(pw->password_hash);
PKCS5_PBKDF2_HMAC(password, (int)strlen(password), PKCS5_PBKDF2_HMAC(password, (int)strlen(password),
pw->salt, sizeof(pw->salt), 20000, pw->salt, sizeof(pw->salt), iterations,
digest, (int)hash_len, pw->password_hash); digest, (int)hash_len, pw->password_hash);
} }

View File

@ -24,14 +24,16 @@ enum mosquitto_pwhash_type{
}; };
#define SALT_LEN 12 #define SALT_LEN 12
#define PW_DEFAULT_ITERATIONS 101
struct mosquitto_pw{ struct mosquitto_pw{
unsigned char password_hash[64]; /* For SHA512 */ unsigned char password_hash[64]; /* For SHA512 */
unsigned char salt[SALT_LEN]; unsigned char salt[SALT_LEN];
int iterations;
enum mosquitto_pwhash_type hashtype; enum mosquitto_pwhash_type hashtype;
}; };
int pw__hash(const char *password, struct mosquitto_pw *pw, bool new_salt); 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__memcmp_const(const void *ptr1, const void *b, size_t len);
int base64_encode(unsigned char *in, unsigned int in_len, char **encoded); int base64_encode(unsigned char *in, unsigned int in_len, char **encoded);
int base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len); int base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len);

View File

@ -33,7 +33,7 @@ static int acl__cleanup(struct mosquitto_db *db, bool reload);
static int unpwd__cleanup(struct mosquitto__unpwd **unpwd, bool reload); static int unpwd__cleanup(struct mosquitto__unpwd **unpwd, bool reload);
static int psk__file_parse(struct mosquitto_db *db, struct mosquitto__unpwd **psk_id, const char *psk_file); static int psk__file_parse(struct mosquitto_db *db, struct mosquitto__unpwd **psk_id, const char *psk_file);
#ifdef WITH_TLS #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); 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 #endif
static int mosquitto_unpwd_check_default(int event, void *event_data, void *userdata); static int mosquitto_unpwd_check_default(int event, void *event_data, void *userdata);
static int mosquitto_acl_check_default(int event, void *event_data, void *userdata); static int mosquitto_acl_check_default(int event, void *event_data, void *userdata);
@ -841,9 +841,19 @@ static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
HASH_ITER(hh, *unpwd, u, tmp){ HASH_ITER(hh, *unpwd, u, tmp){
/* Need to decode password into hashed data + salt. */ /* Need to decode password into hashed data + salt. */
if(u->password){ if(u->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, "$"); token = strtok(u->password, "$");
if(token){ 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")){ if(!strcmp(token, "6")){
hashtype = pw_sha512; hashtype = pw_sha512;
}else if(!strcmp(token, "7")){ }else if(!strcmp(token, "7")){
@ -853,8 +863,28 @@ static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
unpwd__free_item(unpwd, u); unpwd__free_item(unpwd, u);
continue; continue;
} }
if(hashtype == pw_sha512_pbkdf2){
token = strtok(NULL, "$"); token = strtok(NULL, "$");
if(token){ 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); rc = base64__decode(token, &salt, &salt_len);
if(rc == MOSQ_ERR_SUCCESS && salt_len == 12){ if(rc == MOSQ_ERR_SUCCESS && salt_len == 12){
u->salt = salt; u->salt = salt;
@ -879,18 +909,6 @@ static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password salt for user %s, removing entry.", u->username); log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password salt for user %s, removing entry.", u->username);
unpwd__free_item(unpwd, u); 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: Invalid password hash for user %s, removing entry.", u->username);
unpwd__free_item(unpwd, u);
}
}else{
log__printf(NULL, MOSQ_LOG_ERR, "Error: Missing password hash for user %s, removing entry.", u->username);
unpwd__free_item(unpwd, u);
}
} }
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
@ -992,7 +1010,7 @@ static int mosquitto_unpwd_check_default(int event, void *event_data, void *user
if(u->password){ if(u->password){
if(ed->client->password){ if(ed->client->password){
#ifdef WITH_TLS #ifdef WITH_TLS
rc = pw__digest(ed->client->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype); rc = pw__digest(ed->client->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype, u->iterations);
if(rc == MOSQ_ERR_SUCCESS){ if(rc == MOSQ_ERR_SUCCESS){
if(hash_len == u->password_len && !mosquitto__memcmp_const(u->password, hash, hash_len)){ if(hash_len == u->password_len && !mosquitto__memcmp_const(u->password, hash, hash_len)){
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
@ -1293,7 +1311,7 @@ int mosquitto_psk_key_get_default(struct mosquitto_db *db, struct mosquitto *con
} }
#ifdef WITH_TLS #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 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; const EVP_MD *digest;
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
@ -1329,7 +1347,7 @@ int pw__digest(const char *password, const unsigned char *salt, unsigned int sal
}else{ }else{
*hash_len = EVP_MAX_MD_SIZE; *hash_len = EVP_MAX_MD_SIZE;
PKCS5_PBKDF2_HMAC(password, (int)strlen(password), PKCS5_PBKDF2_HMAC(password, (int)strlen(password),
salt, (int)salt_len, 20000, salt, (int)salt_len, iterations,
digest, (int)(*hash_len), hash); digest, (int)(*hash_len), hash);
} }