diff --git a/package/confd/Config.in b/package/confd/Config.in index e1b8dfa1..1b469ce8 100644 --- a/package/confd/Config.in +++ b/package/confd/Config.in @@ -11,3 +11,39 @@ config BR2_PACKAGE_CONFD either the on-system CLI or using NETCONF. https://github.com/kernelkit/infix + +if BR2_PACKAGE_CONFD + +choice + prompt "Default password crypt" + default BR2_PACKAGE_CONFD_YESCRYPT + help + When users set $0$cleartext-password as their password confd + salts and encrypts the "cleartext-password" with this selected + hash algorithm. + + Infix defaults to yescrypt. + +config BR2_PACKAGE_CONFD_MD5CRYPT + bool "md5crypt" + help + +config BR2_PACKAGE_CONFD_SHA256CRYPT + bool "sha256crypt" + +config BR2_PACKAGE_CONFD_SHA512CRYPT + bool "sha512crypt" + +config BR2_PACKAGE_CONFD_YESCRYPT + bool "yescrypt" + +endchoice + +config BR2_PACKAGE_CONFD_DEFAULT_CRYPT + string + default "md5crypt" if BR2_PACKAGE_CONFD_MD5CRYPT + default "sha256crypt" if BR2_PACKAGE_CONFD_SHA256CRYPT + default "sha512crypt" if BR2_PACKAGE_CONFD_SHA512CRYPT + default "yescrypt" if BR2_PACKAGE_CONFD_YESCRYPT + +endif diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 9cab7b52..d5fd5bb9 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -12,7 +12,7 @@ CONFD_LICENSE_FILES = LICENSE CONFD_REDISTRIBUTE = NO CONFD_DEPENDENCIES = host-sysrepo sysrepo netopeer2 jansson libite sysrepo libsrx libglib2 CONFD_AUTORECONF = YES -CONFD_CONF_OPTS += --disable-silent-rules +CONFD_CONF_OPTS += --disable-silent-rules --with-crypt=$(BR2_PACKAGE_CONFD_DEFAULT_CRYPT) CONFD_SYSREPO_SHM_PREFIX = sr_buildroot$(subst /,_,$(CONFIG_DIR))_confd define CONFD_CONF_ENV diff --git a/src/confd/configure.ac b/src/confd/configure.ac index 055bb4ba..4df2bd57 100644 --- a/src/confd/configure.ac +++ b/src/confd/configure.ac @@ -36,6 +36,10 @@ AC_ARG_WITH(login-shell, AS_HELP_STRING([--with-login-shell=shell], [Login shell for new users, default: /bin/false]), [login_shell=$withval], [login_shell=yes]) +AC_ARG_WITH(crypt, + AS_HELP_STRING([--with-crypt=crypt], [Default crypt for $0$cleartext, default: yescrypt]), + [crypt=$withval], [crypt="yescrypt"]) + AS_IF([test "x$enable_containers" = "xyes"], [ AC_DEFINE(CONTAINERS, 1, [Built with container support])]) @@ -44,9 +48,15 @@ AS_IF([test "x$with_login_shell" != "xno"], [ AC_DEFINE_UNQUOTED(LOGIN_SHELL, "$login_shell", [Default: /bin/false])],[ AC_DEFINE_UNQUOTED(LOGIN_SHELL, "/bin/false")]) +AS_IF([test "x$with_crypt" != "xno"], [ + AS_IF([test "x$crypt" = "xyes"], [crypt=yescrypt]) + AC_DEFINE_UNQUOTED(DEFAULT_CRYPT, "$crypt", [Default: yescrypt])],[ + AC_DEFINE_UNQUOTED(DEFAULT_CRYPT, "yescrypt")]) + # Check for pkg-config first, warn if it's not installed PKG_PROG_PKG_CONFIG +PKG_CHECK_MODULES([crypt], [libxcrypt >= 4.4.36]) PKG_CHECK_MODULES([glib], [glib-2.0 >= 2.50 gio-2.0 gio-unix-2.0]) PKG_CHECK_MODULES([jansson], [jansson >= 2.0.0]) PKG_CHECK_MODULES([libite], [libite >= 2.5.0]) @@ -104,6 +114,7 @@ cat < +#include #include #include #include @@ -1219,6 +1220,87 @@ err_release_data: return err; } +static const char *ctp_crypt(void) +{ + struct { + const char *crypt; + const char *prefix; + } list[] = { + { "md5crypt", "$1$" }, + { "sha256crypt", "$5$" }, + { "sha512crypt", "$6$" }, + { "yescrypt", "$y$" }, + }; + size_t i; + + for (i = 0; i < NELEMS(list); i++) { + if (strcmp(list[i].crypt, DEFAULT_CRYPT)) + continue; + + return list[i].prefix; + } + + return "$y$"; /* fallback */ +} + +static int ctp_hash_is_cleartext(const char *hash) +{ + if (strlen(hash) < 3) + return 0; + + if (hash[0] == '$' && hash[1] == '0' && hash[2] == '$') + return 1; + + return 0; +} + +static sr_error_t check_user_ctp(sr_session_ctx_t *session, struct confd *_, struct sr_change *change) +{ + const char *prefix = ctp_crypt(); + char *hash, *salt; + sr_val_t *val; + + val = change->new; + if (!val) + return SR_ERR_OK; + + hash = val->data.string_val; + if (!ctp_hash_is_cleartext(hash)) + return SR_ERR_OK; + + if (strlen(hash) < 4) { + sr_session_set_error(session, NULL, SR_ERR_VALIDATION_FAILED, "Too short password."); + return SR_ERR_VALIDATION_FAILED; + } + + salt = crypt_gensalt(prefix, 0, NULL, 0); + if (!salt) { + sr_session_set_error(session, NULL, SR_ERR_INTERNAL, "error %d generating salt, " + "prefix '%s'.", errno, prefix); + return SR_ERR_INTERNAL; + } + + hash = crypt(hash + 3, salt); + if (!hash) { + sr_session_set_error(session, NULL, SR_ERR_INTERNAL, "failed hashing password, " + "error %d.", errno); + return SR_ERR_INTERNAL; + } + + return sr_set_item_str(session, val->xpath, hash, NULL, 0); +} + +static sr_error_t change_auth_ctp(struct confd *confd, sr_session_ctx_t *session) +{ + sr_error_t err; + + err = _sr_change_iter(session, confd, XPATH_AUTH_"/user[*]/password", check_user_ctp); + if (err) + return err; + + return SR_ERR_OK; +} + static sr_error_t change_auth_check(struct confd *confd, sr_session_ctx_t *session) { sr_error_t err; @@ -1262,10 +1344,16 @@ static int change_auth(sr_session_ctx_t *session, uint32_t sub_id, const char *m { struct confd *confd = (struct confd *)priv; - if (event == SR_EV_CHANGE) + switch (event) { + case SR_EV_UPDATE: + return change_auth_ctp(confd, session); + case SR_EV_CHANGE: return change_auth_check(confd, session); - if (event == SR_EV_DONE) + case SR_EV_DONE: return change_auth_done(confd, session); + default: + break; + } return SR_ERR_OK; } @@ -1670,7 +1758,7 @@ int ietf_system_init(struct confd *confd) os_init(); - REGISTER_CHANGE(confd->session, "ietf-system", XPATH_AUTH_, 0, change_auth, confd, &confd->sub); + REGISTER_CHANGE(confd->session, "ietf-system", XPATH_AUTH_, SR_SUBSCR_UPDATE, change_auth, confd, &confd->sub); REGISTER_OPER(confd->session, "ietf-system", PASSWORD_PATH, auth_cb, confd, 0, &confd->sub); REGISTER_MONITOR(confd->session, "ietf-netconf-acm", "/ietf-netconf-acm:nacm//.", 0, change_nacm, confd, &confd->sub); diff --git a/src/confd/yang/infix-system@2024-06-15.yang b/src/confd/yang/infix-system@2024-06-15.yang index 24058639..020ef7f2 100644 --- a/src/confd/yang/infix-system@2024-06-15.yang +++ b/src/confd/yang/infix-system@2024-06-15.yang @@ -125,14 +125,13 @@ module infix-system { + '|$factory$.*'; } description - "The crypt-hash type is used to store passwords using a hash - function. This type extends the existing crypt-hash type to - support yescrypt as well as the reserved string $factory$, - which is used for device-specific factory default hash. It - is up to the underlying system to define this further, but - one example is to use Vital Product Data (VPD) information, - e.g., an onboard EEPROM where a device hash is stored for - the initial 'admin' user. + "This type is used to store passwords using a hash function. It + extends the IANA crypt-hash type to support yescrypt as well as + a reserved string '$factory$', used for device-specific factory + default hash. It is up to the underlying system to define this + further, one example is to use Vital Product Data (VPD), e.g., + an onboard EEPROM where a device hash is stored for the initial + 'admin' user. A value of this type matches one of the forms: @@ -140,33 +139,31 @@ module infix-system { $$$ $$$$ - The '$0$' prefix signals that the value is clear text. When - such a value is received by the server, a hash value is - calculated, and the string '$$$' or - $$$$ is prepended to the result. This - value is stored in the configuration data store. - If a value starting with '$$', where is not '0', is - received, the server knows that the value already represents a - hashed value and stores it 'as is' in the data store. + The '$0$' prefix signals that the value is clear text, and even + though it is supported, it is *not* recommended! When such a + value is received it passes through multiple subsystems before + a hash value is calculated and the string '$$$' or + $$$$ is prepended and the result is stored + in the configuration data store. The hash function used depend + on end system requirements. + + When any other '$$' prefix is received, the system store it + 'as is' in the configuration data store. When a server needs to verify a password given by a user, it finds the stored password hash string for that user, extracts the salt, and calculates the hash with the salt and given - password as input. If the calculated hash value is the same - as the stored value, the password given by the client is - accepted. + password as input. If the calculated hash value is the same as + the stored value, the password given by the client is accepted. - This type defines the following hash functions: + This type defines the following supported hash functions: id | hash function | feature ---+---------------+------------------- 1 | MD5 | crypt-hash-md5 5 | SHA-256 | crypt-hash-sha-256 6 | SHA-512 | crypt-hash-sha-512 - y | yescrypt | crypt-hash-yescrypt - - The server indicates support for the different hash functions - by advertising the corresponding feature."; + y | yescrypt | crypt-hash-yescrypt"; reference "IEEE Std 1003.1-2008 - crypt() function RFC 1321: The MD5 Message-Digest Algorithm