From d1e0e0076d437b8ca02e4dbe495e47dc1e0af941 Mon Sep 17 00:00:00 2001 From: eeff <33566201+eeff@users.noreply.github.com> Date: Thu, 30 May 2024 14:41:16 +0800 Subject: [PATCH 1/3] feat(restful): check user password strength --- include/neuron/define.h | 4 +- include/neuron/errcodes.h | 2 + plugins/restful/user.c | 100 ++++++++++++++++++++++++++++++++++++++ src/utils/http.c | 2 + 4 files changed, 106 insertions(+), 2 deletions(-) diff --git a/include/neuron/define.h b/include/neuron/define.h index 4f9563f2..57a93b5f 100644 --- a/include/neuron/define.h +++ b/include/neuron/define.h @@ -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 diff --git a/include/neuron/errcodes.h b/include/neuron/errcodes.h index 4921ebe8..46f0ad22 100644 --- a/include/neuron/errcodes.h +++ b/include/neuron/errcodes.h @@ -47,6 +47,8 @@ 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_NODE_EXIST = 2002, NEU_ERR_NODE_NOT_EXIST = 2003, diff --git a/plugins/restful/user.c b/plugins/restful/user.c index ed1c9f3c..d2485e60 100644 --- a/plugins/restful/user.c +++ b/plugins/restful/user.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -343,6 +344,100 @@ 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; +} + neu_user_t *neu_user_new(const char *name, const char *password) { neu_user_t *user = malloc(sizeof(*user)); @@ -415,6 +510,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) { diff --git a/src/utils/http.c b/src/utils/http.c index 7dad6331..17d12ebc 100644 --- a/src/utils/http.c +++ b/src/utils/http.c @@ -338,6 +338,8 @@ 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_TEMPLATE_NAME_TOO_LONG: case NEU_ERR_PLUGIN_NAME_TOO_LONG: case NEU_ERR_PLUGIN_NOT_SUPPORT_TEMPLATE: From 82e930c4b2128f2b9f9728ce6b20366ce7f85bae Mon Sep 17 00:00:00 2001 From: eeff <33566201+eeff@users.noreply.github.com> Date: Thu, 30 May 2024 19:23:46 +0800 Subject: [PATCH 2/3] feat(restful): user password must be encrypted --- include/neuron/errcodes.h | 1 + plugins/restful/normal_handle.c | 43 +++++++++------- plugins/restful/user.c | 88 +++++++++++++++++++++++++++++++++ plugins/restful/user.h | 2 + src/utils/base64.c | 8 ++- src/utils/http.c | 1 + 6 files changed, 123 insertions(+), 20 deletions(-) diff --git a/include/neuron/errcodes.h b/include/neuron/errcodes.h index 46f0ad22..51c7f199 100644 --- a/include/neuron/errcodes.h +++ b/include/neuron/errcodes.h @@ -49,6 +49,7 @@ typedef enum { 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, diff --git a/plugins/restful/normal_handle.c b/plugins/restful/normal_handle.c index 52fb800a..721b6699 100644 --- a/plugins/restful/normal_handle.c +++ b/plugins/restful/normal_handle.c @@ -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); }) } diff --git a/plugins/restful/user.c b/plugins/restful/user.c index d2485e60..4490cd0f 100644 --- a/plugins/restful/user.c +++ b/plugins/restful/user.c @@ -11,6 +11,7 @@ #include "errcodes.h" #include "user.h" +#include "utils/base64.h" #include "utils/log.h" static const unsigned char cov_2char[64] = { @@ -438,6 +439,93 @@ static inline int checkout_password_strength(const char *password) 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)); diff --git a/plugins/restful/user.h b/plugins/restful/user.h index 6d5ce233..8331b301 100644 --- a/plugins/restful/user.h +++ b/plugins/restful/user.h @@ -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 diff --git a/src/utils/base64.c b/src/utils/base64.c index 4dd131a2..8cf95008 100644 --- a/src/utils/base64.c +++ b/src/utils/base64.c @@ -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 } diff --git a/src/utils/http.c b/src/utils/http.c index 17d12ebc..2abc67fd 100644 --- a/src/utils/http.c +++ b/src/utils/http.c @@ -340,6 +340,7 @@ int neu_http_response(nng_aio *aio, neu_err_code_e code, char *content) 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: From 5ea6cfc56bfb5a5973956efab7a849f8136da0d0 Mon Sep 17 00:00:00 2001 From: eeff <33566201+eeff@users.noreply.github.com> Date: Thu, 30 May 2024 20:09:35 +0800 Subject: [PATCH 3/3] feat(restful): use stronger default password --- persistence/0051_2.6.8_user_pass.sql | 29 ++++++++++++++++++++++++++++ src/argparse.c | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 persistence/0051_2.6.8_user_pass.sql diff --git a/persistence/0051_2.6.8_user_pass.sql b/persistence/0051_2.6.8_user_pass.sql new file mode 100644 index 00000000..264ee60c --- /dev/null +++ b/persistence/0051_2.6.8_user_pass.sql @@ -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; diff --git a/src/argparse.c b/src/argparse.c index 25be4d13..081201df 100644 --- a/src/argparse.c +++ b/src/argparse.c @@ -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);