Skip to content

Commit

Permalink
Add support for PBKDF2-SHA512 password hashing.
Browse files Browse the repository at this point in the history
  • Loading branch information
ralight committed Sep 23, 2020
1 parent c927446 commit 5371bd0
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 58 deletions.
1 change: 1 addition & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Broker:
- Add `mosquitto_kick_client_by_clientid()` and `mosquitto_kick_client_by_username()`
functions, which can be used by plugins to disconnect clients.
- Add support for handling $CONTROL/ topics in plugins.
- Add support for PBKDF2-SHA512 password hashing.

Client library:
- Client no longer generates random client ids for v3.1.1 clients, these are
Expand Down
18 changes: 18 additions & 0 deletions man/mosquitto_passwd.1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>mosquitto_passwd</command>
<group>
<arg choice='plain'><option>-H</option> <replaceable>hash</replaceable></arg>
</group>
<group>
<arg choice='plain'><option>-c</option></arg>
<arg choice='plain'><option>-D</option></arg>
Expand All @@ -26,6 +29,9 @@
</cmdsynopsis>
<cmdsynopsis>
<command>mosquitto_passwd</command>
<group>
<arg choice='plain'><option>-H</option> <replaceable>hash</replaceable></arg>
</group>
<arg choice='plain'><option>-b</option></arg>
<arg choice='plain'><replaceable>passwordfile</replaceable></arg>
<arg choice='plain'><replaceable>username</replaceable></arg>
Expand Down Expand Up @@ -74,6 +80,18 @@
file.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-H</option></term>
<listitem>
<para>Choose the hash to use. Can be one of
<replaceable>sha512-pbkdf2</replaceable> or
<replaceable>sha512</replaceable>. Defaults to
<replaceable>sha512-pbkdf2</replaceable>. The
<replaceable>sha512</replaceable> option is provided for
creating password files for use with Mosquitto 1.6
and earlier.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-U</option></term>
<listitem>
Expand Down
10 changes: 8 additions & 2 deletions src/mosquitto_broker_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ enum mosquitto_msg_origin{
mosq_mo_broker = 1
};

enum mosquitto_pwhash_type{
pw_sha512 = 6,
pw_sha512_pbkdf2 = 7
};

struct mosquitto__auth_plugin{
void *lib;
void *user_data;
Expand Down Expand Up @@ -449,14 +454,15 @@ struct mosquitto_client_msg{
};

struct mosquitto__unpwd{
UT_hash_handle hh;
char *username;
char *password;
#ifdef WITH_TLS
unsigned char *salt;
unsigned int password_len;
unsigned int salt_len;
unsigned char *salt;
#endif
UT_hash_handle hh;
enum mosquitto_pwhash_type hashtype;
};

struct mosquitto__acl{
Expand Down
94 changes: 63 additions & 31 deletions src/mosquitto_passwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ and the Eclipse Distribution License is available at

#include "misc_mosq.h"

enum pwhash{
pw_sha512 = 6,
pw_sha512_pbkdf2 = 7,
};

struct cb_helper {
const char *line;
const char *username;
Expand All @@ -69,6 +74,7 @@ static FILE *mpw_tmpfile(void)
#else

static char alphanum[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
static enum pwhash hashtype = pw_sha512_pbkdf2;

static unsigned char tmpfile_path[36];
static FILE *mpw_tmpfile(void)
Expand Down Expand Up @@ -132,12 +138,14 @@ int base64_encode(unsigned char *in, unsigned int in_len, char **encoded)
void print_usage(void)
{
printf("mosquitto_passwd is a tool for managing password files for mosquitto.\n\n");
printf("Usage: mosquitto_passwd [-c | -D] passwordfile username\n");
printf(" mosquitto_passwd -b passwordfile username password\n");
printf("Usage: mosquitto_passwd [-H sha512 | -H sha512-pbkdf2] [-c | -D] passwordfile username\n");
printf(" mosquitto_passwd [-H sha512 | -H sha512-pbkdf2] -b passwordfile username password\n");
printf(" mosquitto_passwd -U passwordfile\n");
printf(" -b : run in batch mode to allow passing passwords on the command line.\n");
printf(" -c : create a new password file. This will overwrite existing files.\n");
printf(" -D : delete the username rather than adding/updating its password.\n");
printf(" -H : specify the hashing algorithm. Defaults to sha512-pbkdf2, which is recommended.\n");
printf(" Mosquitto 1.6 and earlier defaulted to sha512.\n");
printf(" -U : update a plain text password file to use hashed passwords.\n");
printf("\nSee https://mosquitto.org/ for more information.\n\n");
}
Expand Down Expand Up @@ -177,21 +185,28 @@ int output_new_password(FILE *fptr, const char *username, const char *password)
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);
EVP_DigestFinal_ex(&context, hash, &hash_len);
EVP_MD_CTX_cleanup(&context);
EVP_MD_CTX_init(&context);
EVP_DigestInit_ex(&context, digest, NULL);
EVP_DigestUpdate(&context, password, strlen(password));
EVP_DigestUpdate(&context, salt, SALT_LEN);
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);
EVP_DigestFinal_ex(context, hash, &hash_len);
EVP_MD_CTX_free(context);
context = EVP_MD_CTX_new();
EVP_DigestInit_ex(context, digest, NULL);
EVP_DigestUpdate(context, password, strlen(password));
EVP_DigestUpdate(context, salt, SALT_LEN);
EVP_DigestFinal_ex(context, hash, &hash_len);
EVP_MD_CTX_free(context);
#endif
}else{
hash_len = sizeof(hash);
PKCS5_PBKDF2_HMAC(password, strlen(password),
salt, SALT_LEN, 20000,
digest, hash_len, hash);
}

rc = base64_encode(hash, hash_len, &hash64);
if(rc){
Expand All @@ -201,7 +216,7 @@ int output_new_password(FILE *fptr, const char *username, const char *password)
return 1;
}

fprintf(fptr, "%s:$6$%s$%s\n", username, salt64, hash64);
fprintf(fptr, "%s:$%d$%s$%s\n", username, hashtype, salt64, hash64);
free(salt64);
free(hash64);

Expand Down Expand Up @@ -331,10 +346,8 @@ static int update_pwuser_cb(FILE *fptr, FILE *ftmp, const char *username, const
{
int rc = 0;

printf("%s\n", username);
if(strcmp(username, helper->username)){
/* If this isn't the matching user, then writing out the exiting line */
printf("%s\n", line);
fprintf(ftmp, "%s", line);
}else{
/* Write out a new line for our matching username */
Expand Down Expand Up @@ -520,6 +533,7 @@ int main(int argc, char *argv[])
int rc;
bool do_update_file = false;
char *backup_file;
int idx;

signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
Expand All @@ -537,45 +551,63 @@ int main(int argc, char *argv[])
return 1;
}

if(!strcmp(argv[1], "-c")){
idx = 1;
if(!strcmp(argv[1], "-H")){
if(argc < 5){
fprintf(stderr, "Error: -H argument given but not enough other arguments.\n");
return 1;
}
if(!strcmp(argv[2], "sha512")){
hashtype = pw_sha512;
}else if(!strcmp(argv[2], "sha512-pbkdf2")){
hashtype = pw_sha512_pbkdf2;
}else{
fprintf(stderr, "Error: Unknown hash type '%s'\n", argv[2]);
return 1;
}
idx += 2;
argc -= 2;
}

if(!strcmp(argv[idx], "-c")){
create_new = true;
if(argc != 4){
fprintf(stderr, "Error: -c argument given but password file or username missing.\n");
return 1;
}else{
password_file_tmp = argv[2];
username = argv[3];
password_file_tmp = argv[idx+1];
username = argv[idx+2];
}
}else if(!strcmp(argv[1], "-D")){
}else if(!strcmp(argv[idx], "-D")){
delete_user = true;
if(argc != 4){
fprintf(stderr, "Error: -D argument given but password file or username missing.\n");
return 1;
}else{
password_file_tmp = argv[2];
username = argv[3];
password_file_tmp = argv[idx+1];
username = argv[idx+2];
}
}else if(!strcmp(argv[1], "-b")){
}else if(!strcmp(argv[idx], "-b")){
batch_mode = true;
if(argc != 5){
fprintf(stderr, "Error: -b argument given but password file, username or password missing.\n");
return 1;
}else{
password_file_tmp = argv[2];
username = argv[3];
password_cmd = argv[4];
password_file_tmp = argv[idx+1];
username = argv[idx+2];
password_cmd = argv[idx+3];
}
}else if(!strcmp(argv[1], "-U")){
}else if(!strcmp(argv[idx], "-U")){
if(argc != 3){
fprintf(stderr, "Error: -U argument given but password file missing.\n");
return 1;
}else{
do_update_file = true;
password_file_tmp = argv[2];
password_file_tmp = argv[idx+1];
}
}else if(argc == 3){
password_file_tmp = argv[1];
username = argv[2];
password_file_tmp = argv[idx];
username = argv[idx+1];
}else{
print_usage();
return 1;
Expand Down
64 changes: 39 additions & 25 deletions src/security_default.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 psk__file_parse(struct mosquitto_db *db, 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);
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 base64__decode(char *in, unsigned char **decoded, unsigned int *decoded_len);
static int mosquitto__memcmp_const(const void *ptr1, const void *b, size_t len);
#endif
Expand Down Expand Up @@ -773,12 +773,22 @@ static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
unsigned char *password;
unsigned int password_len;
int rc;
int hashtype;

HASH_ITER(hh, *unpwd, u, tmp){
/* Need to decode password into hashed data + salt. */
if(u->password){
token = strtok(u->password, "$");
if(token && !strcmp(token, "6")){
if(token){
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;
}
token = strtok(NULL, "$");
if(token){
rc = base64__decode(token, &salt, &salt_len);
Expand All @@ -792,6 +802,7 @@ static int unpwd__decode_passwords(struct mosquitto__unpwd **unpwd)
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);
Expand Down Expand Up @@ -953,7 +964,7 @@ int mosquitto_unpwd_check_default(struct mosquitto_db *db, struct mosquitto *con
if(u->password){
if(context->password){
#ifdef WITH_TLS
rc = pw__digest(context->password, u->salt, u->salt_len, hash, &hash_len);
rc = pw__digest(context->password, u->salt, u->salt_len, hash, &hash_len, u->hashtype);
if(rc == MOSQ_ERR_SUCCESS){
if(hash_len == u->password_len && !mosquitto__memcmp_const(u->password, hash, hash_len)){
return MOSQ_ERR_SUCCESS;
Expand Down Expand Up @@ -1250,42 +1261,45 @@ int mosquitto_psk_key_get_default(struct mosquitto_db *db, struct mosquitto *con
}

#ifdef WITH_TLS
int pw__digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len)
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)
{
const EVP_MD *digest;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
EVP_MD_CTX context;

digest = EVP_get_digestbyname("sha512");
if(!digest){
// FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n");
return 1;
}

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
EVP_MD_CTX *context;
#endif

digest = EVP_get_digestbyname("sha512");
if(!digest){
// FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n");
return 1;
}

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);
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, strlen(password),
salt, salt_len, 20000,
digest, *hash_len, hash);
}

return MOSQ_ERR_SUCCESS;
}
Expand Down

0 comments on commit 5371bd0

Please sign in to comment.