mirror of
https://github.com/eclipse-mosquitto/mosquitto.git
synced 2026-05-21 12:24:29 +02:00
527 lines
13 KiB
C
527 lines
13 KiB
C
/*
|
|
Copyright (c) 2012-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"
|
|
|
|
#ifdef WITH_TLS
|
|
# include <openssl/opensslv.h>
|
|
# include <openssl/evp.h>
|
|
# include <openssl/rand.h>
|
|
#endif
|
|
#include <string.h>
|
|
|
|
#include "mosquitto.h"
|
|
#include "base64_mosq.h"
|
|
#include "memory_mosq.h"
|
|
#include "password_mosq.h"
|
|
|
|
#ifdef WIN32
|
|
# include <windows.h>
|
|
# include <process.h>
|
|
# ifndef __cplusplus
|
|
# if defined(_MSC_VER) && _MSC_VER < 1900
|
|
# define bool char
|
|
# define true 1
|
|
# define false 0
|
|
# else
|
|
# include <stdbool.h>
|
|
# endif
|
|
# endif
|
|
# define snprintf sprintf_s
|
|
# include <io.h>
|
|
# include <windows.h>
|
|
#else
|
|
# include <stdbool.h>
|
|
#endif
|
|
|
|
#ifdef WITH_ARGON2
|
|
# include <argon2.h>
|
|
# define MOSQ_ARGON2_T 1
|
|
# define MOSQ_ARGON2_M 47104
|
|
# define MOSQ_ARGON2_P 1
|
|
#endif
|
|
|
|
int pw__memcmp_const(const void *a, const void *b, size_t len)
|
|
{
|
|
#ifdef WITH_TLS
|
|
return CRYPTO_memcmp(a, b, len);
|
|
#else
|
|
int rc = 0;
|
|
const volatile char *ac = a;
|
|
const volatile char *bc = b;
|
|
|
|
if(!a || !b) return 1;
|
|
|
|
for(size_t i=0; i<len; i++){
|
|
rc |= ((char *)ac)[i] ^ ((char *)bc)[i];
|
|
}
|
|
return rc;
|
|
#endif
|
|
}
|
|
|
|
/* ==================================================
|
|
* ARGON2
|
|
* ================================================== */
|
|
|
|
static int pw__create_argon2id(struct mosquitto_pw *pw, const char *password)
|
|
{
|
|
#ifdef WITH_ARGON2
|
|
pw->hashtype = pw_argon2id;
|
|
pw->params.argon2id.salt_len = HASH_LEN;
|
|
int rc = RAND_bytes(pw->params.argon2id.salt, (int)pw->params.argon2id.salt_len);
|
|
if(!rc){
|
|
return MOSQ_ERR_UNKNOWN;
|
|
}
|
|
|
|
size_t encoded_len = argon2_encodedlen(MOSQ_ARGON2_T, MOSQ_ARGON2_M, MOSQ_ARGON2_P,
|
|
(uint32_t)pw->params.argon2id.salt_len, sizeof(pw->params.argon2id.password_hash), Argon2_id);
|
|
|
|
free(pw->encoded_password);
|
|
pw->encoded_password = calloc(1, encoded_len+1);
|
|
|
|
rc = argon2id_hash_encoded(MOSQ_ARGON2_T, MOSQ_ARGON2_M, MOSQ_ARGON2_P,
|
|
password, strlen(password),
|
|
pw->params.argon2id.salt, pw->params.argon2id.salt_len,
|
|
HASH_LEN,
|
|
pw->encoded_password, encoded_len+1);
|
|
|
|
if(rc == ARGON2_OK){
|
|
return MOSQ_ERR_SUCCESS;
|
|
}else{
|
|
return MOSQ_ERR_UNKNOWN;
|
|
}
|
|
#else
|
|
return MOSQ_ERR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
static int pw__verify_argon2id(struct mosquitto_pw *pw, const char *password)
|
|
{
|
|
#ifdef WITH_ARGON2
|
|
int rc = argon2id_verify(pw->encoded_password,
|
|
password, strlen(password));
|
|
|
|
if(rc == ARGON2_OK){
|
|
return MOSQ_ERR_SUCCESS;
|
|
}else{
|
|
return MOSQ_ERR_AUTH;
|
|
}
|
|
#else
|
|
return MOSQ_ERR_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
static int pw__decode_argon2id(struct mosquitto_pw *pw, const char *password)
|
|
{
|
|
#ifdef WITH_ARGON2
|
|
char *new_password = strdup(password);
|
|
|
|
if(new_password){
|
|
free(pw->encoded_password);
|
|
pw->encoded_password = new_password;
|
|
return MOSQ_ERR_SUCCESS;
|
|
}else{
|
|
return MOSQ_ERR_NOMEM;
|
|
}
|
|
#else
|
|
return MOSQ_ERR_NOT_SUPPORTED;
|
|
#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_argon2id:
|
|
return pw__create_argon2id(pw, password);
|
|
case pw_sha512_pbkdf2:
|
|
return pw__create_sha512_pbkdf2(pw, password);
|
|
case pw_sha512:
|
|
return pw__create_sha512(pw, password);
|
|
default:
|
|
return pw__create_argon2id(pw, password);
|
|
}
|
|
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
|
|
int pw__verify(struct mosquitto_pw *pw, const char *password)
|
|
{
|
|
switch(pw->hashtype){
|
|
case pw_argon2id:
|
|
return pw__verify_argon2id(pw, password);
|
|
case pw_sha512_pbkdf2:
|
|
return pw__verify_sha512_pbkdf2(pw, password);
|
|
case pw_sha512:
|
|
return pw__verify_sha512(pw, password);
|
|
}
|
|
|
|
return MOSQ_ERR_AUTH;
|
|
}
|
|
|
|
int pw__encode(struct mosquitto_pw *pw)
|
|
{
|
|
switch(pw->hashtype){
|
|
case pw_argon2id:
|
|
return MOSQ_ERR_SUCCESS;
|
|
case pw_sha512_pbkdf2:
|
|
return pw__encode_sha512_pbkdf2(pw);
|
|
case pw_sha512:
|
|
return pw__encode_sha512(pw);
|
|
}
|
|
|
|
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 if(!strncmp(password, "$argon2id$", strlen("$argon2id$"))){
|
|
pw->hashtype = pw_argon2id;
|
|
return pw__decode_argon2id(pw, password);
|
|
}else{
|
|
return MOSQ_ERR_INVAL;
|
|
}
|
|
}
|