From 743039ce99b5fa1f880c07b07b1c8ece9fca4309 Mon Sep 17 00:00:00 2001 From: Radek Vykydal Date: Mon, 13 Nov 2023 14:35:28 +0100 Subject: [PATCH] webui: crypt user password by default before passing it to backend NOTE: using the same method as the current Gtk GUI --- src/components/AnacondaWizard.jsx | 10 +++++--- src/components/users/Accounts.jsx | 9 ++++++- src/scripts/encrypt-user-pw.py | 42 +++++++++++++++++++++++++++++++ test/check-users | 3 +-- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/scripts/encrypt-user-pw.py diff --git a/src/components/AnacondaWizard.jsx b/src/components/AnacondaWizard.jsx index cbbe264d89..c90537d9d5 100644 --- a/src/components/AnacondaWizard.jsx +++ b/src/components/AnacondaWizard.jsx @@ -39,7 +39,7 @@ import { getDefaultScenario } from "./storage/InstallationScenario.jsx"; import { MountPointMapping, getPageProps as getMountPointMappingProps } from "./storage/MountPointMapping.jsx"; import { DiskEncryption, getStorageEncryptionState, getPageProps as getDiskEncryptionProps } from "./storage/DiskEncryption.jsx"; import { InstallationLanguage, getPageProps as getInstallationLanguageProps } from "./localization/InstallationLanguage.jsx"; -import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsToDbusUsers } from "./users/Accounts.jsx"; +import { Accounts, getPageProps as getAccountsProps, getAccountsState, accountsToDbusUsers, cryptUserPassword } from "./users/Accounts.jsx"; import { InstallationProgress } from "./installation/InstallationProgress.jsx"; import { ReviewConfiguration, ReviewConfigurationConfirmModal, getPageProps as getReviewConfigurationProps } from "./review/ReviewConfiguration.jsx"; import { exitGui } from "../helpers/exit.js"; @@ -362,8 +362,12 @@ const Footer = ({ }, }); } else if (activeStep.id === "accounts") { - setUsers(accountsToDbusUsers(accounts)); - onNext(); + cryptUserPassword(accounts.password) + .then(cryptedPassword => { + const users = accountsToDbusUsers({ ...accounts, password: cryptedPassword }); + setUsers(users); + onNext(); + }, onCritFail({ context: N_("Password ecryption failed.") })); } else { onNext(); } diff --git a/src/components/users/Accounts.jsx b/src/components/users/Accounts.jsx index 8ab65260ae..bce838273b 100644 --- a/src/components/users/Accounts.jsx +++ b/src/components/users/Accounts.jsx @@ -17,6 +17,8 @@ import cockpit from "cockpit"; import React, { useState, useEffect } from "react"; +import * as python from "python.js"; +import encryptUserPw from "../../scripts/encrypt-user-pw.py"; import { Form, @@ -45,12 +47,17 @@ export function getAccountsState ( }; } +export const cryptUserPassword = async (password) => { + const crypted = await python.spawn(encryptUserPw, password, { err: "message", environ: ["LC_ALL=C.UTF-8"] }); + return crypted; +}; + export const accountsToDbusUsers = (accounts) => { return [{ name: cockpit.variant("s", accounts.userAccount || ""), gecos: cockpit.variant("s", accounts.fullName || ""), password: cockpit.variant("s", accounts.password || ""), - "is-crypted": cockpit.variant("b", false), + "is-crypted": cockpit.variant("b", true), groups: cockpit.variant("as", ["wheel"]), }]; }; diff --git a/src/scripts/encrypt-user-pw.py b/src/scripts/encrypt-user-pw.py new file mode 100644 index 0000000000..91ee9af413 --- /dev/null +++ b/src/scripts/encrypt-user-pw.py @@ -0,0 +1,42 @@ +import crypt +from random import SystemRandom as sr +import sys + +# Using the function from pyanaconda/core/users.py + +def crypt_password(password): + """Crypt a password. + + Process a password with appropriate salted one-way algorithm. + + :param str password: password to be crypted + :returns: crypted representation of the original password + :rtype: str + """ + # yescrypt is not supported by Python's crypt module, + # so we need to generate the setting ourselves + b64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + setting = "$y$j9T$" + "".join(sr().choice(b64) for _sc in range(24)) + + # and try to compute the password hash using our yescrypt setting + try: + cryptpw = crypt.crypt(password, setting) + + # Fallback to sha512crypt, if yescrypt is not supported + except OSError: + sys.stderr.write("yescrypt is not supported, falling back to sha512crypt\n") + try: + cryptpw = crypt.crypt(password, crypt.METHOD_SHA512) + except OSError as exc: + raise RuntimeError( + "Unable to encrypt password: unsupported algorithm {}".format(crypt.METHOD_SHA512) + ) from exc + + return cryptpw + + +try: + print(crypt_password(sys.argv[1]), end="") +except Exception as e: + sys.stderr.write(str(e) + "\n") + sys.exit(1) diff --git a/test/check-users b/test/check-users index 2d44d7f978..59fe08e848 100755 --- a/test/check-users +++ b/test/check-users @@ -44,8 +44,7 @@ class TestUsers(anacondalib.VirtInstallMachineCase): users = u.dbus_get_users() self.assertIn('"groups" as 1 "wheel"', users) - self.assertIn('"is-crypted" b false', users) - self.assertIn('"password" s "password"', users) + self.assertIn('"is-crypted" b true', users) if __name__ == '__main__': test_main()