mirror of
https://github.com/emqx/neuron.git
synced 2026-10-26 03:40:01 +08:00
Merge pull request #2049 from eeff/2.6-encrypt_pass
v2.6: encrypt user password
This commit is contained in:
@@ -35,8 +35,8 @@
|
||||
#define NEU_DRIVER_TAG_CACHE_EXPIRE_TIME 60
|
||||
#define NEU_APP_SUBSCRIBE_MSG_SIZE 4
|
||||
#define NEU_TAG_FLOAG_PRECISION_MAX 17
|
||||
#define NEU_USER_PASSWORD_MIN_LEN 4
|
||||
#define NEU_USER_PASSWORD_MAX_LEN 16
|
||||
#define NEU_USER_PASSWORD_MIN_LEN 8
|
||||
#define NEU_USER_PASSWORD_MAX_LEN 32
|
||||
#define NEU_LOG_LEVEL_LEN 12
|
||||
#define NEU_FILE_PATH_LEN 128
|
||||
#define NEU_MSG_MAX_SIZE 2048
|
||||
|
||||
@@ -47,6 +47,9 @@ typedef enum {
|
||||
NEU_ERR_COMMAND_EXECUTION_FAILED = 1014,
|
||||
NEU_ERR_IP_ADDRESS_INVALID = 1015,
|
||||
NEU_ERR_IP_ADDRESS_IN_USE = 1016,
|
||||
NEU_ERR_WEAK_PASSWORD_ALPHABET = 1017,
|
||||
NEU_ERR_WEAK_PASSWORD_PATTERN = 1018,
|
||||
NEU_ERR_DECRYPT_PASSWORD_FAILED = 1019,
|
||||
|
||||
NEU_ERR_NODE_EXIST = 2002,
|
||||
NEU_ERR_NODE_NOT_EXIST = 2003,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* NEURON IIoT System for Industry 4.0
|
||||
* Copyright (C) 2020-2024 EMQ Technologies Co., Ltd All rights reserved.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
**/
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
-- use stronger password
|
||||
UPDATE users
|
||||
SET
|
||||
password = "$5$PwFeXpBBIBZuZdZl$MUEmeqpMfQA9fzQ3s9bojsBhZOHJwKHiukeWyutsSR6"
|
||||
WHERE
|
||||
name = "admin"
|
||||
AND password = "$5$PwFeXpBBIBZuZdZl$fP8fFPWCLoaWcnVXVSR.3Xi8TEqCvX92gjhowNNn6S4";
|
||||
|
||||
COMMIT;
|
||||
@@ -53,23 +53,20 @@ void handle_login(nng_aio *aio)
|
||||
NEU_PROCESS_HTTP_REQUEST(
|
||||
aio, neu_json_login_req_t, neu_json_decode_login_req, {
|
||||
neu_json_login_resp_t login_resp = { 0 };
|
||||
neu_user_t * user = neu_load_user(req->name);
|
||||
int pass_len = strlen(req->pass);
|
||||
neu_user_t * user = NULL;
|
||||
char *password = neu_decrypt_user_password(req->pass);
|
||||
|
||||
if (NULL == user) {
|
||||
if (NULL == password) {
|
||||
nlog_error("could not decrypt user `%s` password", req->name);
|
||||
NEU_JSON_RESPONSE_ERROR(NEU_ERR_DECRYPT_PASSWORD_FAILED, {
|
||||
neu_http_response(aio, error_code.error, result_error);
|
||||
});
|
||||
} else if (NULL == (user = neu_load_user(req->name))) {
|
||||
nlog_error("could not find user `%s`", req->name);
|
||||
NEU_JSON_RESPONSE_ERROR(NEU_ERR_INVALID_USER_OR_PASSWORD, {
|
||||
neu_http_response(aio, error_code.error, result_error);
|
||||
});
|
||||
} else if (pass_len < NEU_USER_PASSWORD_MIN_LEN ||
|
||||
pass_len > NEU_USER_PASSWORD_MAX_LEN) {
|
||||
nlog_error("user `%s` password too short or too long",
|
||||
req->name);
|
||||
NEU_JSON_RESPONSE_ERROR(NEU_ERR_INVALID_PASSWORD_LEN, {
|
||||
neu_http_response(aio, error_code.error, result_error);
|
||||
});
|
||||
} else if (neu_user_check_password(user, req->pass)) {
|
||||
|
||||
} else if (neu_user_check_password(user, password)) {
|
||||
char *token = NULL;
|
||||
char *result = NULL;
|
||||
|
||||
@@ -95,6 +92,7 @@ void handle_login(nng_aio *aio)
|
||||
});
|
||||
}
|
||||
|
||||
free(password);
|
||||
neu_user_free(user);
|
||||
})
|
||||
}
|
||||
@@ -105,21 +103,28 @@ void handle_password(nng_aio *aio)
|
||||
aio, neu_json_password_req_t, neu_json_decode_password_req, {
|
||||
neu_user_t *user = NULL;
|
||||
int rv = 0;
|
||||
int pass_len = strlen(req->new_pass);
|
||||
char * new_pass = neu_decrypt_user_password(req->new_pass);
|
||||
char * old_pass = neu_decrypt_user_password(req->old_pass);
|
||||
int pass_len = new_pass ? strlen(new_pass) : 0;
|
||||
|
||||
if (pass_len < NEU_USER_PASSWORD_MIN_LEN ||
|
||||
pass_len > NEU_USER_PASSWORD_MAX_LEN) {
|
||||
if (NULL == new_pass) {
|
||||
nlog_error("could not decrypt user `%s` new pass", req->name);
|
||||
rv = NEU_ERR_DECRYPT_PASSWORD_FAILED;
|
||||
} else if (NULL == old_pass) {
|
||||
nlog_error("could not decrypt user `%s` old pass", req->name);
|
||||
rv = NEU_ERR_DECRYPT_PASSWORD_FAILED;
|
||||
} else if (pass_len < NEU_USER_PASSWORD_MIN_LEN ||
|
||||
pass_len > NEU_USER_PASSWORD_MAX_LEN) {
|
||||
nlog_error("user `%s` new password too short or too long",
|
||||
req->name);
|
||||
rv = NEU_ERR_INVALID_PASSWORD_LEN;
|
||||
} else if (NULL == (user = neu_load_user(req->name))) {
|
||||
nlog_error("could not find user `%s`", req->name);
|
||||
rv = NEU_ERR_INVALID_USER_OR_PASSWORD;
|
||||
} else if (!neu_user_check_password(user, req->old_pass)) {
|
||||
} else if (!neu_user_check_password(user, old_pass)) {
|
||||
nlog_error("user `%s` password check fail", req->name);
|
||||
rv = NEU_ERR_INVALID_USER_OR_PASSWORD;
|
||||
} else if (0 !=
|
||||
(rv = neu_user_update_password(user, req->new_pass))) {
|
||||
} else if (0 != (rv = neu_user_update_password(user, new_pass))) {
|
||||
nlog_error("user `%s` update password fail", req->name);
|
||||
} else if (0 != (rv = neu_save_user(user))) {
|
||||
nlog_error("user `%s` persist fail", req->name);
|
||||
@@ -129,6 +134,8 @@ void handle_password(nng_aio *aio)
|
||||
neu_http_response(aio, error_code.error, result_error);
|
||||
});
|
||||
|
||||
free(new_pass);
|
||||
free(old_pass);
|
||||
neu_user_free(user);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
@@ -10,6 +11,7 @@
|
||||
|
||||
#include "errcodes.h"
|
||||
#include "user.h"
|
||||
#include "utils/base64.h"
|
||||
#include "utils/log.h"
|
||||
|
||||
static const unsigned char cov_2char[64] = {
|
||||
@@ -343,6 +345,187 @@ end:
|
||||
return hash;
|
||||
}
|
||||
|
||||
static bool check_password_alphabet(const char *password)
|
||||
{
|
||||
int kind = 0;
|
||||
|
||||
// check special characters
|
||||
const char *special = "_`~!@#$%^&*()+=|{}':;',\\[\\].<>/"
|
||||
"?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\n\r\t";
|
||||
if (NULL != strpbrk(password, special)) {
|
||||
++kind;
|
||||
}
|
||||
|
||||
// check digits
|
||||
if (NULL != strpbrk(password, "0123456789")) {
|
||||
++kind;
|
||||
}
|
||||
|
||||
// check uppercase letters
|
||||
if (NULL != strpbrk(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")) {
|
||||
++kind;
|
||||
}
|
||||
|
||||
// check lowercase letters
|
||||
if (NULL != strpbrk(password, "abcdefghijklmnopqrstuvwxyz")) {
|
||||
++kind;
|
||||
}
|
||||
|
||||
return kind > 2;
|
||||
}
|
||||
|
||||
static bool check_password_pattern(const char *password)
|
||||
{
|
||||
const int n = strlen(password) - 2;
|
||||
|
||||
// password must not contain character of length 3
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (password[i] == password[i + 1] && password[i] == password[i + 2]) {
|
||||
nlog_error("password has three `%c` in a row", password[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char *patterns[] = {
|
||||
"qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm",
|
||||
"ik,", "ol.", "p;/", "esz", "rdx", "tfc", "ygv",
|
||||
"uhb", "ijn", "okm", "pl,", "[;.", "]'/", "1qa",
|
||||
"2ws", "3ed", "4rf", "5tg", "6yh", "7uj", "8ik",
|
||||
"9ol", "0p;", "-['", "=[;", "-pl", "0ok", "9ij",
|
||||
"8uh", "7yg", "6tf", "5rd", "4es", "3wa", "root",
|
||||
"admin", "mysql", "oracle", "system", "windows", "linux", "java",
|
||||
"python", "unix", "test",
|
||||
};
|
||||
|
||||
// password must not contain any pattern
|
||||
for (unsigned i = 0; i < sizeof(patterns) / sizeof(patterns[0]); ++i) {
|
||||
if (NULL != strstr(password, patterns[i])) {
|
||||
nlog_error("password has weak pattern `%s`", patterns[i]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char *sequences[] = {
|
||||
"qwertyuiop[]\\", "asdfghjkl;'", "zxcvbnm,./",
|
||||
"901234567890-=", "9876543210", "abcdefghijklmnopqrstuvwxyz"
|
||||
};
|
||||
|
||||
// password must not have common sub string of length 3 with any sequence
|
||||
for (unsigned i = 0; i < sizeof(sequences) / sizeof(sequences[0]); ++i) {
|
||||
const char *seq = sequences[i];
|
||||
const int m = strlen(seq) - 2;
|
||||
for (int j = 0; j < m; ++j) {
|
||||
for (int k = 0; k < n; ++k) {
|
||||
if (seq[j] == password[k] && seq[j + 1] == password[k + 1] &&
|
||||
seq[j + 2] == password[k + 2]) {
|
||||
nlog_error("password has weak pattern `%.3s`", &seq[j]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline int checkout_password_strength(const char *password)
|
||||
{
|
||||
if (false == check_password_alphabet(password)) {
|
||||
return NEU_ERR_WEAK_PASSWORD_ALPHABET;
|
||||
}
|
||||
if (false == check_password_pattern(password)) {
|
||||
return NEU_ERR_WEAK_PASSWORD_PATTERN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline EVP_CIPHER_CTX *aes_ctx_new()
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned char key[32] = { 90, 45, 54, 10, 182, 19, 52, 130,
|
||||
109, 151, 202, 252, 248, 177, 136, 35,
|
||||
234, 204, 188, 2, 72, 215, 207, 52,
|
||||
22, 5, 193, 52, 228, 224, 157, 28 };
|
||||
unsigned char iv[16] = { 239, 227, 132, 238, 4, 225, 127, 67,
|
||||
202, 77, 31, 79, 203, 70, 149, 132 };
|
||||
|
||||
EVP_CIPHER_CTX *e = EVP_CIPHER_CTX_new();
|
||||
if (NULL == e) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(e);
|
||||
|
||||
ret = EVP_DecryptInit_ex(e, EVP_aes_256_cbc(), NULL, key, iv);
|
||||
if (0 == ret) {
|
||||
EVP_CIPHER_CTX_free(e);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static char *aes_decrypt(unsigned char *cipher_text, int *len)
|
||||
{
|
||||
char *plain_text = NULL;
|
||||
int plain_text_len = *len + EVP_CIPHER_block_size(EVP_aes_256_cbc());
|
||||
int f_len = 0;
|
||||
int ret = 0;
|
||||
|
||||
EVP_CIPHER_CTX *ctx = aes_ctx_new();
|
||||
if (NULL == ctx) {
|
||||
nlog_warn("alloc ctx fail");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
plain_text = calloc(1, plain_text_len + 1);
|
||||
if (NULL == plain_text) {
|
||||
nlog_warn("alloc plain_text fail");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = EVP_DecryptUpdate(ctx, (unsigned char *) plain_text, &plain_text_len,
|
||||
(unsigned char *) cipher_text, *len);
|
||||
if (ret <= 0) {
|
||||
free(plain_text);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
nlog_warn("decrypt update error: %d", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = EVP_DecryptFinal_ex(
|
||||
ctx, (unsigned char *) plain_text + plain_text_len, &f_len);
|
||||
if (ret <= 0) {
|
||||
free(plain_text);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
nlog_warn("decrypt final error: %d", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*len = plain_text_len + f_len;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
return plain_text;
|
||||
}
|
||||
|
||||
char *neu_decrypt_user_password(const char *crypted_password)
|
||||
{
|
||||
int len = 0;
|
||||
char * password = NULL;
|
||||
unsigned char *cipher_text = neu_decode64(&len, crypted_password);
|
||||
if (cipher_text && (password = aes_decrypt(cipher_text, &len))) {
|
||||
password[len] = '\0';
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if ('\0' == password[i]) {
|
||||
free(password);
|
||||
password = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(cipher_text);
|
||||
return password;
|
||||
}
|
||||
|
||||
neu_user_t *neu_user_new(const char *name, const char *password)
|
||||
{
|
||||
neu_user_t *user = malloc(sizeof(*user));
|
||||
@@ -415,6 +598,11 @@ int neu_save_user(neu_user_t *user)
|
||||
|
||||
int neu_user_update_password(neu_user_t *user, const char *new_password)
|
||||
{
|
||||
int rv = checkout_password_strength(new_password);
|
||||
if (0 != rv) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
char *salt = user->hash + 3;
|
||||
char *hash = hash_password(new_password, salt);
|
||||
if (NULL == hash) {
|
||||
|
||||
@@ -41,6 +41,8 @@ int neu_user_update_password(neu_user_t *user, const char *new_password);
|
||||
neu_user_t *neu_load_user(const char *name);
|
||||
int neu_save_user(neu_user_t *user);
|
||||
|
||||
char *neu_decrypt_user_password(const char *crypted_password);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ static inline int reset_password()
|
||||
neu_persist_user_info_t info = {
|
||||
.name = "admin",
|
||||
.hash =
|
||||
"$5$PwFeXpBBIBZuZdZl$fP8fFPWCLoaWcnVXVSR.3Xi8TEqCvX92gjhowNNn6S4",
|
||||
"$5$PwFeXpBBIBZuZdZl$MUEmeqpMfQA9fzQ3s9bojsBhZOHJwKHiukeWyutsSR6",
|
||||
};
|
||||
|
||||
int rv = neu_persister_create(g_config_dir);
|
||||
|
||||
+6
-2
@@ -68,10 +68,14 @@ int Base64Decode(const char *b64message, unsigned char **buffer, int *length)
|
||||
BIO_set_flags(
|
||||
bio, BIO_FLAGS_BASE64_NO_NL); // Do not use newlines to flush buffer
|
||||
*length = BIO_read(bio, *buffer, strlen(b64message));
|
||||
assert(*length == decodeLen); // length should equal decodeLen, else
|
||||
// something went horribly wrong
|
||||
BIO_free_all(bio);
|
||||
|
||||
if (*length != decodeLen) {
|
||||
free(*buffer);
|
||||
*buffer = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (0); // success
|
||||
}
|
||||
|
||||
|
||||
@@ -338,6 +338,9 @@ int neu_http_response(nng_aio *aio, neu_err_code_e code, char *content)
|
||||
case NEU_ERR_GROUP_NAME_TOO_LONG:
|
||||
case NEU_ERR_INVALID_PASSWORD_LEN:
|
||||
case NEU_ERR_DUPLICATE_PASSWORD:
|
||||
case NEU_ERR_WEAK_PASSWORD_ALPHABET:
|
||||
case NEU_ERR_WEAK_PASSWORD_PATTERN:
|
||||
case NEU_ERR_DECRYPT_PASSWORD_FAILED:
|
||||
case NEU_ERR_TEMPLATE_NAME_TOO_LONG:
|
||||
case NEU_ERR_PLUGIN_NAME_TOO_LONG:
|
||||
case NEU_ERR_PLUGIN_NOT_SUPPORT_TEMPLATE:
|
||||
|
||||
Reference in New Issue
Block a user