diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 2941600154..edd4619374 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -97,6 +97,10 @@ ifneq (,$(filter trace,$(USEMODULE))) USEMODULE += ztimer_usec endif +ifneq (,$(filter shell_lock,$(USEMODULE))) + USEMODULE += ztimer_msec +endif + ifneq (,$(filter ssp,$(USEMODULE))) FEATURES_REQUIRED += ssp endif diff --git a/sys/Makefile.include b/sys/Makefile.include index a2f6112d0d..64e51fae6e 100644 --- a/sys/Makefile.include +++ b/sys/Makefile.include @@ -155,3 +155,7 @@ endif ifneq (,$(filter test_utils_netdev_eth_minimal,$(USEMODULE))) CFLAGS += -DCONFIG_NETDEV_REGISTER_SIGNAL endif + +ifneq (,$(filter shell_lock,$(USEMODULE))) + include $(RIOTBASE)/sys/shell_lock/Makefile.include +endif diff --git a/sys/include/shell_lock.h b/sys/include/shell_lock.h new file mode 100644 index 0000000000..e44eaa1299 --- /dev/null +++ b/sys/include/shell_lock.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_shell_lock Shell lock + * @ingroup sys + * @brief Simple module to provide a password protection for the shell. + * @experimental This module is an experimental feature and only shows as a proof of concept how + * the shell could be protected with a password. Do not expect relevant security from + * it for production, since Man-in-the-Middle attacks are possible depending on the + * used connection method! + * + * @{ + * + * @file + * @brief Shell interface definition + */ + +#ifndef SHELL_LOCK_H +#define SHELL_LOCK_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "shell.h" + +#ifdef MODULE_SHELL_LOCK + #ifndef CONFIG_SHELL_LOCK_PASSWORD + #error Using MODULE_SHELL_LOCK requires defining CONFIG_SHELL_LOCK_PASSWORD + #endif /* CONFIG_SHELL_LOCK_PASSWORD */ +#endif /* MODULE_SHELL_LOCK */ + +/** + * @brief Lock the login process after given attempts of failed logins for + * a few seconds + */ +#define CONFIG_SHELL_LOCK_ATTEMPTS_BEFORE_TIME_LOCK 3 + +/** + * @brief Entry point for the lock mechanism. If locked, the user will + * be asked for a password. This function won't return until the + * correct password has been entered. + * + * @param[in] line_buf Buffer for reading in the password from stdin + * @param[in] buf_size Buffer size + */ +void shell_lock_checkpoint(char *line_buf, int buf_size); + +/** + * @brief Returns true, if the shell is in the locked state. + * + * @return Whether the shell is locked or not. + */ +bool shell_lock_is_locked(void); + +#ifdef MODULE_SHELL_LOCK_AUTO_LOCKING +/** + * @brief Restart the timeout interval before the shell is locked + * automatically. + */ +void shell_lock_auto_lock_refresh(void); +#endif /* MODULE_SHELL_LOCK_AUTO_LOCKING */ + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_LOCK_H */ +/** @} */ diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 0730f8b1e9..c84a11b64d 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -38,6 +38,7 @@ #include "kernel_defines.h" #include "xfa.h" #include "shell.h" +#include "shell_lock.h" /* define shell command cross file array */ XFA_INIT_CONST(shell_command_t*, shell_commands_xfa); @@ -61,6 +62,10 @@ XFA_INIT_CONST(shell_command_t*, shell_commands_xfa); #define PARSE_ESCAPE_MASK 0x4; +extern void shell_lock_checkpoint(char *line_buf, int len); +extern bool shell_lock_is_locked(void); +extern void shell_lock_reset(void); + enum parse_state { PARSE_BLANK = 0x0, @@ -106,6 +111,7 @@ static shell_command_handler_t find_handler( const shell_command_t *command_list, char *command) { shell_command_handler_t handler = NULL; + if (command_list != NULL) { handler = search_commands(command_list, command); } @@ -404,7 +410,7 @@ static inline void new_line(void) * @return EOF, if the end of the input stream was reached. * @return -ENOBUFS if the buffer size was exceeded. */ -static int readline(char *buf, size_t size) +int readline(char *buf, size_t size) /* needed externally by module shell_lock */ { int curr_pos = 0; bool length_exceeded = false; @@ -471,11 +477,21 @@ static int readline(char *buf, size_t size) void shell_run_once(const shell_command_t *shell_commands, char *line_buf, int len) { + if (IS_USED(MODULE_SHELL_LOCK)) { + shell_lock_checkpoint(line_buf, len); + } + print_prompt(); while (1) { int res = readline(line_buf, len); + if (IS_USED(MODULE_SHELL_LOCK)) { + if (shell_lock_is_locked()) { + break; + } + } + switch (res) { case EOF: diff --git a/sys/shell_lock/Makefile b/sys/shell_lock/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/shell_lock/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/shell_lock/Makefile.include b/sys/shell_lock/Makefile.include new file mode 100644 index 0000000000..f3bd483f7b --- /dev/null +++ b/sys/shell_lock/Makefile.include @@ -0,0 +1,4 @@ +$(shell $(COLOR_ECHO) "$(COLOR_YELLOW)shell_lock is an experimental feature and only shows as a \ + proof of concept how the shell could be protected with a password. Do not expect relevant \ + security from it for production, since Man-in-the-Middle attacks are possible depending on the \ + used connection method!$(COLOR_RESET)" 1>&2) diff --git a/sys/shell_lock/shell_lock.c b/sys/shell_lock/shell_lock.c new file mode 100644 index 0000000000..3e775512bc --- /dev/null +++ b/sys/shell_lock/shell_lock.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_shell_lock + * @{ + * + * @file + * @brief Module to lock the running shell with a password. + * + * The Shell is proceeded only when the valid password was entered by the user. + * After 3 (default) failed attempts, the input is blocked for a few seconds to + * slow down brute force attacks. + * Does not make use of any cryptographic features yet. + * + * @author Hendrik van Essen + * + * @} + */ + +#include +#include +#include +#include +#include + +#include "xtimer.h" + +#include "shell_lock.h" + +#if defined(MODULE_NEWLIB) || defined(MODULE_PICOLIBC) + #define flush_if_needed() fflush(stdout) +#else + #define flush_if_needed() +#endif /* MODULE_NEWLIB || MODULE_PICOLIBC */ + +static bool _shell_is_locked = true; + +/* defined in shell.c */ +extern int readline(char *buf, size_t size); + +static int _lock_handler(int argc, char **argv) +{ + (void) argc; + (void) argv; + + _shell_is_locked = true; + + return 0; +} + +SHELL_COMMAND(lock, "Lock the shell", _lock_handler); + +static inline void _print_password_prompt(void) +{ + printf("Password: "); + flush_if_needed(); +} + +/* Implementation of strcmp that does not return after the first difference + * which could give away information about the first n correct characters of + * the password. The length of the loop is only dependent on the input string. + * Don't optimize this function by a compiler. */ +static bool __attribute__((optimize("O0"))) _safe_strcmp(const char* input, const char* pwd) +{ + bool the_same = true; + + int input_len = strlen(input); + int pwd_len = strlen(pwd); + + int input_index = 0; + int pwd_index = 0; + + do { + if (input[input_index] != pwd[pwd_index]) { + the_same &= false; + } + else { + the_same &= true; + } + + /* keep indices at last index of respective string */ + if (input_index < input_len) { + input_index++; + } + + if (pwd_index < pwd_len) { + pwd_index++; + } + + } while (input[input_index] != '\0'); + + if (input_len != pwd_len) { + /* substring of the password doesn't count */ + return false; + } + + return the_same; +} + +static bool _login(char *line_buf, size_t buf_size) +{ + _print_password_prompt(); + + if (readline(line_buf, buf_size) > 0) { + return _safe_strcmp(line_buf, CONFIG_SHELL_LOCK_PASSWORD); + } + + return false; +} + +/** + * Repeatedly prompt for the password. + * + * This function won't return until the correct password has been entered. + */ +static void _login_barrier(char *line_buf, size_t buf_size) +{ + while (1) { + int attempts = CONFIG_SHELL_LOCK_ATTEMPTS_BEFORE_TIME_LOCK; + + while (attempts--) { + if (_login(line_buf, buf_size)) { + return; + } + puts("Wrong password"); + ztimer_sleep(ZTIMER_MSEC, 1000); + } + ztimer_sleep(ZTIMER_MSEC, 7000); + } +} + +bool shell_lock_is_locked(void) +{ + return _shell_is_locked; +} + +void shell_lock_checkpoint(char *line_buf, int buf_size) +{ + if (_shell_is_locked) { + printf("The shell is locked. Enter a valid password to unlock.\n\n"); + + _login_barrier(line_buf, buf_size); + + _shell_is_locked = false; + } +}