From 2541ce2c1af87f74a9feb35a1cbfc20ff8d04e4b Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:43 +0300 Subject: [PATCH 01/11] reboot-mode: add support for reboot mode control A new driver uclass is created to handle the reboot mode control. The new uclass driver is updating an environment variable with the configured reboot mode. The mode is extracted from a map provided at initialization time. The map contains a list of modes and associated ids. Signed-off-by: Nandor Han Reviewed-by: Simon Glass --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/reboot-mode/Kconfig | 18 +++ drivers/reboot-mode/Makefile | 7 ++ drivers/reboot-mode/reboot-mode-uclass.c | 134 +++++++++++++++++++++++ include/dm/uclass-id.h | 1 + include/reboot-mode/reboot-mode.h | 56 ++++++++++ 7 files changed, 219 insertions(+) create mode 100644 drivers/reboot-mode/Kconfig create mode 100644 drivers/reboot-mode/Makefile create mode 100644 drivers/reboot-mode/reboot-mode-uclass.c create mode 100644 include/reboot-mode/reboot-mode.h diff --git a/drivers/Kconfig b/drivers/Kconfig index c9c812b752..417d6f88c2 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -92,6 +92,8 @@ source "drivers/qe/Kconfig" source "drivers/ram/Kconfig" +source "drivers/reboot-mode/Kconfig" + source "drivers/remoteproc/Kconfig" source "drivers/reset/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 4081289104..82d3c98e06 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -95,6 +95,7 @@ obj-y += dfu/ obj-$(CONFIG_PCH) += pch/ obj-y += phy/allwinner/ obj-y += phy/marvell/ +obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode/ obj-y += phy/rockchip/ obj-y += phy/socionext/ obj-y += rtc/ diff --git a/drivers/reboot-mode/Kconfig b/drivers/reboot-mode/Kconfig new file mode 100644 index 0000000000..0edc3209d7 --- /dev/null +++ b/drivers/reboot-mode/Kconfig @@ -0,0 +1,18 @@ +# +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c), Vaisala Oyj +# + +menu "Reboot Mode Support" + +config DM_REBOOT_MODE + bool "Enable reboot mode using Driver Model" + depends on DM + default n + help + Enable support for reboot mode control. This will allow users to + adjust the boot process based on reboot mode parameter + passed to U-Boot. + +endmenu diff --git a/drivers/reboot-mode/Makefile b/drivers/reboot-mode/Makefile new file mode 100644 index 0000000000..2ab0fddac9 --- /dev/null +++ b/drivers/reboot-mode/Makefile @@ -0,0 +1,7 @@ +# +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c), Vaisala Oyj +# + +obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode-uclass.o diff --git a/drivers/reboot-mode/reboot-mode-uclass.c b/drivers/reboot-mode/reboot-mode-uclass.c new file mode 100644 index 0000000000..bb7a355fbf --- /dev/null +++ b/drivers/reboot-mode/reboot-mode-uclass.c @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c), Vaisala Oyj + */ + +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +int dm_reboot_mode_update(struct udevice *dev) +{ + struct reboot_mode_ops *ops = reboot_mode_get_ops(dev); + u32 rebootmode; + int ret, i; + + assert(ops); + + if (!ops->get) + return -ENOSYS; + + ret = ops->get(dev, &rebootmode); + if (ret < 0) { + dev_err(dev, "Failed to retrieve the reboot mode value\n"); + return ret; + } + + const struct reboot_mode_uclass_platdata *plat_data = + dev_get_uclass_plat(dev); + + for (i = 0; i < plat_data->count; i++) { + if (plat_data->modes[i].mode_id == rebootmode) { + ret = env_set(plat_data->env_variable, + plat_data->modes[i].mode_name); + if (ret) { + dev_err(dev, "Failed to set env: %s\n", + plat_data->env_variable); + return ret; + } + } + } + + if (ops->set) { + /* Clear the value */ + rebootmode = 0; + ret = ops->set(dev, rebootmode); + if (ret) { + dev_err(dev, "Failed to clear the reboot mode\n"); + return ret; + } + } + + return 0; +} + +int dm_reboot_mode_pre_probe(struct udevice *dev) +{ + struct reboot_mode_uclass_platdata *plat_data; + + plat_data = dev_get_uclass_plat(dev); + if (!plat_data) + return -EINVAL; + +#if CONFIG_IS_ENABLED(OF_CONTROL) + const int node = dev_of_offset(dev); + const char *mode_prefix = "mode-"; + const int mode_prefix_len = strlen(mode_prefix); + int property; + const u32 *propvalue; + const char *propname; + + plat_data->env_variable = fdt_getprop(gd->fdt_blob, + node, + "u-boot,env-variable", + NULL); + if (!plat_data->env_variable) + plat_data->env_variable = "reboot-mode"; + + plat_data->count = 0; + + fdt_for_each_property_offset(property, gd->fdt_blob, node) { + propvalue = fdt_getprop_by_offset(gd->fdt_blob, + property, &propname, NULL); + if (!propvalue) { + dev_err(dev, "Could not get the value for property %s\n", + propname); + return -EINVAL; + } + + if (!strncmp(propname, mode_prefix, mode_prefix_len)) + plat_data->count++; + } + + plat_data->modes = devm_kcalloc(dev, plat_data->count, + sizeof(struct reboot_mode_mode), 0); + + struct reboot_mode_mode *next = plat_data->modes; + + fdt_for_each_property_offset(property, gd->fdt_blob, node) { + propvalue = fdt_getprop_by_offset(gd->fdt_blob, + property, &propname, NULL); + if (!propvalue) { + dev_err(dev, "Could not get the value for property %s\n", + propname); + return -EINVAL; + } + + if (!strncmp(propname, mode_prefix, mode_prefix_len)) { + next->mode_name = &propname[mode_prefix_len]; + next->mode_id = fdt32_to_cpu(*propvalue); + + next++; + } + } +#else + if (!plat_data->env_variable) + plat_data->env_variable = "reboot-mode"; + +#endif + + return 0; +} + +UCLASS_DRIVER(reboot_mode) = { + .name = "reboot-mode", + .id = UCLASS_REBOOT_MODE, + .pre_probe = dm_reboot_mode_pre_probe, + .per_device_plat_auto = + sizeof(struct reboot_mode_uclass_platdata), +}; diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index d800f679d5..9d474533ba 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -92,6 +92,7 @@ enum uclass_id { UCLASS_PWRSEQ, /* Power sequence device */ UCLASS_QFW, /* QEMU firmware config device */ UCLASS_RAM, /* RAM controller */ + UCLASS_REBOOT_MODE, /* Reboot mode */ UCLASS_REGULATOR, /* Regulator device */ UCLASS_REMOTEPROC, /* Remote Processor device */ UCLASS_RESET, /* Reset controller device */ diff --git a/include/reboot-mode/reboot-mode.h b/include/reboot-mode/reboot-mode.h new file mode 100644 index 0000000000..86b51f881c --- /dev/null +++ b/include/reboot-mode/reboot-mode.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c), Vaisala Oyj + */ + +#ifndef REBOOT_MODE_REBOOT_MODE_H__ +#define REBOOT_MODE_REBOOT_MODE_H__ + +#include +#include + +struct reboot_mode_mode { + const char *mode_name; + u32 mode_id; +}; + +struct reboot_mode_uclass_platdata { + struct reboot_mode_mode *modes; + u8 count; + const char *env_variable; +}; + +struct reboot_mode_ops { + /** + * get() - get the current reboot mode value + * + * Returns the current value from the reboot mode backing store. + * + * @dev: Device to read from + * @rebootmode: Address to save the current reboot mode value + */ + int (*get)(struct udevice *dev, u32 *rebootmode); + + /** + * set() - set a reboot mode value + * + * Sets the value in the reboot mode backing store. + * + * @dev: Device to read from + * @rebootmode: New reboot mode value to store + */ + int (*set)(struct udevice *dev, u32 rebootmode); +}; + +/* Access the operations for a reboot mode device */ +#define reboot_mode_get_ops(dev) ((struct reboot_mode_ops *)(dev)->driver->ops) + +/** + * dm_reboot_mode_update() - Update the reboot mode env variable. + * + * @dev: Device to read from + * @return 0 if OK, -ve on error + */ +int dm_reboot_mode_update(struct udevice *dev); + +#endif /* REBOOT_MODE_REBOOT_MODE_H__ */ From f9db2f16cb6fc7b6d05b0e70de65881bc97ba5c2 Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:44 +0300 Subject: [PATCH 02/11] reboot-mode: read the boot mode from GPIOs status A use case for controlling the boot mode is when the user wants to control the device boot by pushing a button without needing to go in user-space. Add a new backed for reboot mode where GPIOs are used to control the reboot-mode. The driver is able to scan a predefined list of GPIOs and return the magic value. Having the modes associated with the magic value generated based on the GPIO values, allows the reboot mode uclass to select the proper mode. Signed-off-by: Nandor Han Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 8 ++ configs/sandbox_defconfig | 2 + .../reboot-mode/reboot-mode-gpio.txt | 20 +++ drivers/reboot-mode/Kconfig | 9 ++ drivers/reboot-mode/Makefile | 1 + drivers/reboot-mode/reboot-mode-gpio.c | 128 ++++++++++++++++++ include/reboot-mode/reboot-mode-gpio.h | 32 +++++ test/dm/Makefile | 1 + test/dm/reboot-mode.c | 42 ++++++ 9 files changed, 243 insertions(+) create mode 100644 doc/device-tree-bindings/reboot-mode/reboot-mode-gpio.txt create mode 100644 drivers/reboot-mode/reboot-mode-gpio.c create mode 100644 include/reboot-mode/reboot-mode-gpio.h create mode 100644 test/dm/reboot-mode.c diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 0cee15a0ea..2347f4cbe4 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -59,6 +59,14 @@ }; }; + reboot-mode0 { + compatible = "reboot-mode-gpio"; + gpios = <&gpio_c 0 GPIO_ACTIVE_HIGH>, <&gpio_c 1 GPIO_ACTIVE_HIGH>; + u-boot,env-variable = "bootstatus"; + mode-test = <0x01>; + mode-download = <0x03>; + }; + audio: audio-codec { compatible = "sandbox,audio-codec"; #sound-dai-cells = <1>; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 1655bb1e8a..6931b176ed 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -290,3 +290,5 @@ CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y +CONFIG_DM_REBOOT_MODE=y +CONFIG_DM_REBOOT_MODE_GPIO=y diff --git a/doc/device-tree-bindings/reboot-mode/reboot-mode-gpio.txt b/doc/device-tree-bindings/reboot-mode/reboot-mode-gpio.txt new file mode 100644 index 0000000000..bb209d2742 --- /dev/null +++ b/doc/device-tree-bindings/reboot-mode/reboot-mode-gpio.txt @@ -0,0 +1,20 @@ +GPIO Reboot Mode Configuration + +Required Properties: +- compatible: must be "reboot-mode-gpio". +- gpios: list of gpios that are used to calculate the reboot-mode magic value. + Every gpio represents a bit in the magic value in the same order + as defined in device tree. +- modes: list of properties that define the modes and associated unique ids. + +Optional Properties: +- u-boot,env-variable: used to save the reboot mode (default: reboot-mode). + +Example: + reboot-mode { + compatible = "reboot-mode-gpio"; + gpios = <&gpio1 2 GPIO_ACTIVE_LOW>, <&gpio2 6 GPIO_ACTIVE_HIGH>; + u-boot,env-variable = "bootstatus"; + mode-test = <0x00000001>; + mode-download = <0x00000002>; + }; diff --git a/drivers/reboot-mode/Kconfig b/drivers/reboot-mode/Kconfig index 0edc3209d7..ff65e2031a 100644 --- a/drivers/reboot-mode/Kconfig +++ b/drivers/reboot-mode/Kconfig @@ -15,4 +15,13 @@ config DM_REBOOT_MODE adjust the boot process based on reboot mode parameter passed to U-Boot. +config DM_REBOOT_MODE_GPIO + bool "Use GPIOs as reboot mode backend" + depends on DM_REBOOT_MODE + default n + help + Use GPIOs to control the reboot mode. This will allow users to boot + a device in a specific mode by using a GPIO that can be controlled + outside U-Boot. + endmenu diff --git a/drivers/reboot-mode/Makefile b/drivers/reboot-mode/Makefile index 2ab0fddac9..04917be4f4 100644 --- a/drivers/reboot-mode/Makefile +++ b/drivers/reboot-mode/Makefile @@ -5,3 +5,4 @@ # obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode-uclass.o +obj-$(CONFIG_DM_REBOOT_MODE_GPIO) += reboot-mode-gpio.o diff --git a/drivers/reboot-mode/reboot-mode-gpio.c b/drivers/reboot-mode/reboot-mode-gpio.c new file mode 100644 index 0000000000..305174736e --- /dev/null +++ b/drivers/reboot-mode/reboot-mode-gpio.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c), Vaisala Oyj + */ + +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static int reboot_mode_get(struct udevice *dev, u32 *buf) +{ + int ret; + struct reboot_mode_gpio_platdata *plat_data; + + if (!buf) + return -EINVAL; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + ret = dm_gpio_get_values_as_int(plat_data->gpio_desc, + plat_data->gpio_count); + if (ret < 0) + return ret; + + *buf = ret; + + return 0; +} + +static int reboot_mode_probe(struct udevice *dev) +{ + struct reboot_mode_gpio_platdata *plat_data; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + int ret; + +#if CONFIG_IS_ENABLED(OF_CONTROL) + ret = gpio_get_list_count(dev, "gpios"); + if (ret < 0) + return ret; + + plat_data->gpio_count = ret; +#endif + + if (plat_data->gpio_count <= 0) + return -EINVAL; + + plat_data->gpio_desc = devm_kcalloc(dev, plat_data->gpio_count, + sizeof(struct gpio_desc), 0); + if (!plat_data->gpio_desc) + return -ENOMEM; + +#if CONFIG_IS_ENABLED(OF_CONTROL) + ret = gpio_request_list_by_name(dev, "gpios", plat_data->gpio_desc, + plat_data->gpio_count, GPIOD_IS_IN); + if (ret < 0) + return ret; +#else + for (int i = 0; i < plat_data->gpio_count; i++) { + struct reboot_mode_gpio_config *gpio = + plat_data->gpios_config + i; + struct gpio_desc *desc = plat_data->gpio_desc + i; + + ret = uclass_get_device_by_seq(UCLASS_GPIO, + gpio->gpio_dev_offset, + &desc->dev); + if (ret < 0) + return ret; + + desc->flags = gpio->flags; + desc->offset = gpio->gpio_offset; + + ret = dm_gpio_request(desc, ""); + if (ret < 0) + return ret; + + ret = dm_gpio_set_dir(desc); + if (ret < 0) + return ret; + } +#endif + return 0; +} + +static int reboot_mode_remove(struct udevice *dev) +{ + struct reboot_mode_gpio_platdata *plat_data; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + return gpio_free_list(dev, plat_data->gpio_desc, plat_data->gpio_count); +} + +#if CONFIG_IS_ENABLED(OF_CONTROL) +static const struct udevice_id reboot_mode_ids[] = { + { .compatible = "reboot-mode-gpio", 0 }, + { } +}; +#endif + +static const struct reboot_mode_ops reboot_mode_gpio_ops = { + .get = reboot_mode_get, +}; + +U_BOOT_DRIVER(reboot_mode_gpio) = { + .name = "reboot-mode-gpio", + .id = UCLASS_REBOOT_MODE, + .probe = reboot_mode_probe, + .remove = reboot_mode_remove, +#if CONFIG_IS_ENABLED(OF_CONTROL) + .of_match = reboot_mode_ids, +#endif + .plat_auto = sizeof(struct reboot_mode_gpio_platdata), + .ops = &reboot_mode_gpio_ops, +}; diff --git a/include/reboot-mode/reboot-mode-gpio.h b/include/reboot-mode/reboot-mode-gpio.h new file mode 100644 index 0000000000..16b1185c69 --- /dev/null +++ b/include/reboot-mode/reboot-mode-gpio.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) Vaisala Oyj. + */ + +#ifndef REBOOT_MODE_REBOOT_MODE_GPIO_H_ +#define REBOOT_MODE_REBOOT_MODE_GPIO_H_ + +#include + +/* + * In case of initializing the driver statically (using U_BOOT_DEVICE macro), + * we can use this struct to declare the pins used. + */ + +#if !CONFIG_IS_ENABLED(OF_CONTROL) +struct reboot_mode_gpio_config { + int gpio_dev_offset; + int gpio_offset; + int flags; +}; +#endif + +struct reboot_mode_gpio_platdata { + struct gpio_desc *gpio_desc; +#if !CONFIG_IS_ENABLED(OF_CONTROL) + struct reboot_mode_gpio_config *gpios_config; +#endif + int gpio_count; +}; + +#endif /* REBOOT_MODE_REBOOT_MODE_GPIO_H_ */ diff --git a/test/dm/Makefile b/test/dm/Makefile index 9ef9171a1c..d5c42e7643 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_AXI) += axi.o obj-$(CONFIG_BLK) += blk.o obj-$(CONFIG_BUTTON) += button.o obj-$(CONFIG_DM_BOOTCOUNT) += bootcount.o +obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode.o obj-$(CONFIG_CLK) += clk.o clk_ccf.o obj-$(CONFIG_CPU) += cpu.o obj-$(CONFIG_CROS_EC) += cros_ec.o diff --git a/test/dm/reboot-mode.c b/test/dm/reboot-mode.c new file mode 100644 index 0000000000..66aa4793f7 --- /dev/null +++ b/test/dm/reboot-mode.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) 2018 Theobroma Systems Design und Consulting GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int dm_test_reboot_mode_gpio(struct unit_test_state *uts) +{ + struct udevice *gpio_dev; + struct udevice *rm_dev; + int gpio0_offset = 0; + int gpio1_offset = 1; + + uclass_get_device_by_name(UCLASS_GPIO, "pinmux-gpios", &gpio_dev); + + /* Prepare the GPIOs for "download" mode */ + sandbox_gpio_set_direction(gpio_dev, gpio0_offset, 0); + sandbox_gpio_set_direction(gpio_dev, gpio1_offset, 0); + sandbox_gpio_set_value(gpio_dev, gpio0_offset, 1); + sandbox_gpio_set_value(gpio_dev, gpio1_offset, 1); + + ut_assertok(uclass_get_device_by_name(UCLASS_REBOOT_MODE, + "reboot-mode0", &rm_dev)); + ut_assertok(dm_reboot_mode_update(rm_dev)); + + ut_asserteq_str("download", env_get("bootstatus")); + + return 0; +} + +DM_TEST(dm_test_reboot_mode_gpio, + UT_TESTF_PROBE_TEST | UT_TESTF_SCAN_FDT | UT_TESTF_FLAT_TREE); From c74675bd904b6ce9d5820a80f27793c0583fd54c Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:45 +0300 Subject: [PATCH 03/11] reboot-mode: read the boot mode from RTC memory RTC devices could provide battery-backed memory that can be used for storing the reboot mode magic value. Add a new reboot-mode back-end that uses RTC to store the reboot-mode magic value. The driver also supports both endianness modes. Signed-off-by: Nandor Han Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 10 ++ configs/sandbox_defconfig | 1 + .../reboot-mode/reboot-mode-rtc.txt | 22 +++ drivers/reboot-mode/Kconfig | 9 ++ drivers/reboot-mode/Makefile | 1 + drivers/reboot-mode/reboot-mode-rtc.c | 127 ++++++++++++++++++ include/reboot-mode/reboot-mode-rtc.h | 16 +++ test/dm/reboot-mode.c | 29 ++++ 8 files changed, 215 insertions(+) create mode 100644 doc/device-tree-bindings/reboot-mode/reboot-mode-rtc.txt create mode 100644 drivers/reboot-mode/reboot-mode-rtc.c create mode 100644 include/reboot-mode/reboot-mode-rtc.h diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 2347f4cbe4..d5976318d1 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -67,6 +67,16 @@ mode-download = <0x03>; }; + reboot_mode1: reboot-mode@14 { + compatible = "reboot-mode-rtc"; + rtc = <&rtc_0>; + reg = <0x30 4>; + u-boot,env-variable = "bootstatus"; + big-endian; + mode-test = <0x21969147>; + mode-download = <0x51939147>; + }; + audio: audio-codec { compatible = "sandbox,audio-codec"; #sound-dai-cells = <1>; diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 6931b176ed..1766dc018b 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -292,3 +292,4 @@ CONFIG_UT_TIME=y CONFIG_UT_DM=y CONFIG_DM_REBOOT_MODE=y CONFIG_DM_REBOOT_MODE_GPIO=y +CONFIG_DM_REBOOT_MODE_RTC=y diff --git a/doc/device-tree-bindings/reboot-mode/reboot-mode-rtc.txt b/doc/device-tree-bindings/reboot-mode/reboot-mode-rtc.txt new file mode 100644 index 0000000000..23aa12c88c --- /dev/null +++ b/doc/device-tree-bindings/reboot-mode/reboot-mode-rtc.txt @@ -0,0 +1,22 @@ +RTC Reboot Mode Configuration + +Required Properties: +- compatible: must be "reboot-mode-rtc". +- rtc: reference to the rtc device used. +- reg: start register and the number of bytes used. Maximum 4 bytes supported. +- modes: list of properties that define the modes and associated unique ids. + +Optional Properties: +- u-boot,env-variable: used to save the reboot mode (default: reboot-mode). +- big-endian: if the magic value is stored in big-endian. (default: false). + +Example: + reboot-mode-rtc { + compatible = "reboot-mode-rtc"; + rtc = <&rtc_0>; + reg = <0x14 4>; + u-boot,env-variable = "bootstatus"; + big-endian; + modes-test = <0x21969147>; + modes-download = <0x51939147>; + }; diff --git a/drivers/reboot-mode/Kconfig b/drivers/reboot-mode/Kconfig index ff65e2031a..ac67bfcef6 100644 --- a/drivers/reboot-mode/Kconfig +++ b/drivers/reboot-mode/Kconfig @@ -24,4 +24,13 @@ config DM_REBOOT_MODE_GPIO a device in a specific mode by using a GPIO that can be controlled outside U-Boot. +config DM_REBOOT_MODE_RTC + bool "Use RTC as reboot mode backend" + depends on DM_REBOOT_MODE + default n + help + Use RTC non volatile memory to control the reboot mode. This will allow users to boot + a device in a specific mode by using a register(s) that can be controlled + outside U-Boot (e.g. Kernel). + endmenu diff --git a/drivers/reboot-mode/Makefile b/drivers/reboot-mode/Makefile index 04917be4f4..2c13780ced 100644 --- a/drivers/reboot-mode/Makefile +++ b/drivers/reboot-mode/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_DM_REBOOT_MODE) += reboot-mode-uclass.o obj-$(CONFIG_DM_REBOOT_MODE_GPIO) += reboot-mode-gpio.o +obj-$(CONFIG_DM_REBOOT_MODE_RTC) += reboot-mode-rtc.o diff --git a/drivers/reboot-mode/reboot-mode-rtc.c b/drivers/reboot-mode/reboot-mode-rtc.c new file mode 100644 index 0000000000..972d0cdbcb --- /dev/null +++ b/drivers/reboot-mode/reboot-mode-rtc.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c), Vaisala Oyj + */ + +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +static int reboot_mode_get(struct udevice *dev, u32 *buf) +{ + if (!buf) + return -EINVAL; + + int ret; + u8 *val = (u8 *)buf; + struct reboot_mode_rtc_platdata *plat_data; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + for (int i = 0; i < plat_data->size; i++) { + ret = rtc_read8(plat_data->rtc, plat_data->addr + i); + if (ret < 0) + return ret; + + val[i] = ret; + } + + if (plat_data->is_big_endian) + *buf = __be32_to_cpu(*buf); + else + *buf = __le32_to_cpu(*buf); + + return 0; +} + +static int reboot_mode_set(struct udevice *dev, u32 buf) +{ + int ret; + u8 *val; + struct reboot_mode_rtc_platdata *plat_data; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + if (plat_data->is_big_endian) + buf = __cpu_to_be32(buf); + else + buf = __cpu_to_le32(buf); + + val = (u8 *)&buf; + + for (int i = 0; i < plat_data->size; i++) { + ret = rtc_write8(plat_data->rtc, (plat_data->addr + i), val[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +#if CONFIG_IS_ENABLED(OF_CONTROL) +static int reboot_mode_ofdata_to_platdata(struct udevice *dev) +{ + struct ofnode_phandle_args phandle_args; + struct reboot_mode_rtc_platdata *plat_data; + + plat_data = dev_get_plat(dev); + if (!plat_data) + return -EINVAL; + + if (dev_read_phandle_with_args(dev, "rtc", NULL, 0, 0, &phandle_args)) { + dev_err(dev, "RTC device not specified\n"); + return -ENOENT; + } + + if (uclass_get_device_by_ofnode(UCLASS_RTC, phandle_args.node, + &plat_data->rtc)) { + dev_err(dev, "could not get the RTC device\n"); + return -ENODEV; + } + + plat_data->addr = + dev_read_addr_size_index(dev, 0, (fdt_size_t *)&plat_data->size); + if (plat_data->addr == FDT_ADDR_T_NONE) { + dev_err(dev, "Invalid RTC address\n"); + return -EINVAL; + } + if (plat_data->size > sizeof(u32)) { + dev_err(dev, "Invalid reg size\n"); + return -EINVAL; + } + + plat_data->is_big_endian = ofnode_read_bool(dev_ofnode(dev), "big-endian"); + + return 0; +} + +static const struct udevice_id reboot_mode_ids[] = { + { .compatible = "reboot-mode-rtc", 0 }, + {} +}; +#endif + +static const struct reboot_mode_ops reboot_mode_rtc_ops = { + .get = reboot_mode_get, + .set = reboot_mode_set, +}; + +U_BOOT_DRIVER(reboot_mode_rtc) = { + .name = "reboot-mode-rtc", + .id = UCLASS_REBOOT_MODE, +#if CONFIG_IS_ENABLED(OF_CONTROL) + .of_match = reboot_mode_ids, + .of_to_plat = reboot_mode_ofdata_to_platdata, +#endif + .plat_auto = sizeof(struct reboot_mode_rtc_platdata), + .ops = &reboot_mode_rtc_ops, +}; diff --git a/include/reboot-mode/reboot-mode-rtc.h b/include/reboot-mode/reboot-mode-rtc.h new file mode 100644 index 0000000000..3613678f63 --- /dev/null +++ b/include/reboot-mode/reboot-mode-rtc.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c), Vaisala Oyj + */ + +#ifndef REBOOT_MODE_REBOOT_MODE_RTC_H_ +#define REBOOT_MODE_REBOOT_MODE_RTC_H_ + +struct reboot_mode_rtc_platdata { + struct udevice *rtc; + bool is_big_endian; + int addr; + size_t size; +}; + +#endif /* REBOOT_MODE_REBOOT_MODE_RTC_H_ */ diff --git a/test/dm/reboot-mode.c b/test/dm/reboot-mode.c index 66aa4793f7..fbb9c3a542 100644 --- a/test/dm/reboot-mode.c +++ b/test/dm/reboot-mode.c @@ -9,10 +9,13 @@ #include #include #include +#include #include #include #include #include +#include +#include static int dm_test_reboot_mode_gpio(struct unit_test_state *uts) { @@ -40,3 +43,29 @@ static int dm_test_reboot_mode_gpio(struct unit_test_state *uts) DM_TEST(dm_test_reboot_mode_gpio, UT_TESTF_PROBE_TEST | UT_TESTF_SCAN_FDT | UT_TESTF_FLAT_TREE); + +static int dm_test_reboot_mode_rtc(struct unit_test_state *uts) +{ + struct udevice *rtc_dev; + struct udevice *rm_dev; + u32 read_val; + u32 test_magic_val = cpu_to_be32(0x21969147); + + uclass_get_device_by_name(UCLASS_RTC, "rtc@43", + &rtc_dev); + dm_rtc_write(rtc_dev, REG_AUX0, (u8 *)&test_magic_val, 4); + + ut_assertok(uclass_get_device_by_name(UCLASS_REBOOT_MODE, + "reboot-mode@14", &rm_dev)); + ut_assertok(dm_reboot_mode_update(rm_dev)); + + ut_asserteq_str("test", env_get("bootstatus")); + + dm_rtc_read(rtc_dev, REG_AUX0, (u8 *)&read_val, 4); + ut_asserteq(read_val, 0); + + return 0; +} + +DM_TEST(dm_test_reboot_mode_rtc, + UT_TESTF_PROBE_TEST | UT_TESTF_SCAN_FDT | UT_TESTF_FLAT_TREE); From 26dd9936574864155b989b9f14319ca2779f0598 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:33 +0200 Subject: [PATCH 04/11] lib: add crypt subsystem Add the basic functionality required to support the standard crypt format. The files crypt-sha256.c and crypt-sha512.c originate from libxcrypt and their formatting is therefor retained. The integration is done via a crypt_compare() function in crypt.c. ``` libxcrypt $ git describe --long --always --all tags/v4.4.17-0-g6b110bc ``` Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher --- include/crypt.h | 13 ++ lib/Kconfig | 1 + lib/Makefile | 1 + lib/crypt/Kconfig | 28 ++++ lib/crypt/Makefile | 7 + lib/crypt/alg-sha256.h | 17 ++ lib/crypt/alg-sha512.h | 17 ++ lib/crypt/crypt-port.h | 28 ++++ lib/crypt/crypt-sha256.c | 313 +++++++++++++++++++++++++++++++++++++ lib/crypt/crypt-sha512.c | 328 +++++++++++++++++++++++++++++++++++++++ lib/crypt/crypt.c | 73 +++++++++ test/Kconfig | 10 ++ test/lib/Makefile | 1 + test/lib/test_crypt.c | 44 ++++++ 14 files changed, 881 insertions(+) create mode 100644 include/crypt.h create mode 100644 lib/crypt/Kconfig create mode 100644 lib/crypt/Makefile create mode 100644 lib/crypt/alg-sha256.h create mode 100644 lib/crypt/alg-sha512.h create mode 100644 lib/crypt/crypt-port.h create mode 100644 lib/crypt/crypt-sha256.c create mode 100644 lib/crypt/crypt-sha512.c create mode 100644 lib/crypt/crypt.c create mode 100644 test/lib/test_crypt.c diff --git a/include/crypt.h b/include/crypt.h new file mode 100644 index 0000000000..e0be2832ff --- /dev/null +++ b/include/crypt.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2020 Steffen Jaeckel */ + +/** + * Compare should with the processed passphrase. + * + * @should The crypt-style string to compare against + * @passphrase The plaintext passphrase + * @equal Pointer to an int where the result is stored + * '0' = unequal + * '1' = equal + */ +void crypt_compare(const char *should, const char *passphrase, int *equal); diff --git a/lib/Kconfig b/lib/Kconfig index ad0cd52edd..ad4d75e0a4 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -297,6 +297,7 @@ config AES source lib/rsa/Kconfig source lib/crypto/Kconfig +source lib/crypt/Kconfig config TPM bool "Trusted Platform Module (TPM) Support" diff --git a/lib/Makefile b/lib/Makefile index d8055d36d6..5cd0c9c638 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -65,6 +65,7 @@ obj-$(CONFIG_HASH) += hash-checksum.o obj-$(CONFIG_SHA1) += sha1.o obj-$(CONFIG_SHA256) += sha256.o obj-$(CONFIG_SHA512_ALGO) += sha512.o +obj-$(CONFIG_CRYPT_PW) += crypt/ obj-$(CONFIG_$(SPL_)ZLIB) += zlib/ obj-$(CONFIG_$(SPL_)ZSTD) += zstd/ diff --git a/lib/crypt/Kconfig b/lib/crypt/Kconfig new file mode 100644 index 0000000000..5495ae8d4c --- /dev/null +++ b/lib/crypt/Kconfig @@ -0,0 +1,28 @@ +menuconfig CRYPT_PW + bool "Add crypt support for password-based unlock" + depends on AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION + help + Enable support for crypt-style hashed passphrases. + This will then be used as the mechanism of choice to + verify whether the entered password to unlock the + console is correct or not. + +if CRYPT_PW + +config CRYPT_PW_SHA256 + bool "Provide sha256crypt" + select SHA256 + select SHA256_ALGO + help + Enables support for the sha256crypt password-hashing algorithm. + The prefix is "$5$". + +config CRYPT_PW_SHA512 + bool "Provide sha512crypt" + select SHA512 + select SHA512_ALGO + help + Enables support for the sha512crypt password-hashing algorithm. + The prefix is "$6$". + +endif diff --git a/lib/crypt/Makefile b/lib/crypt/Makefile new file mode 100644 index 0000000000..d53b72b0c7 --- /dev/null +++ b/lib/crypt/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright (c) 2021, Steffen Jaeckel + +obj-$(CONFIG_CRYPT_PW) += crypt.o +obj-$(CONFIG_CRYPT_PW_SHA256) += crypt-sha256.o +obj-$(CONFIG_CRYPT_PW_SHA512) += crypt-sha512.o diff --git a/lib/crypt/alg-sha256.h b/lib/crypt/alg-sha256.h new file mode 100644 index 0000000000..e4b29c9f31 --- /dev/null +++ b/lib/crypt/alg-sha256.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2020 Steffen Jaeckel */ + +#ifndef USE_HOSTCC +#include "common.h" +#else +#include +#endif + +#include "u-boot/sha256.h" + +#define INCLUDE_sha256crypt 1 + +#define SHA256_CTX sha256_context +#define SHA256_Init sha256_starts +#define SHA256_Update(c, i, l) sha256_update(c, (const void *)i, l) +#define SHA256_Final(b, c) sha256_finish(c, b) diff --git a/lib/crypt/alg-sha512.h b/lib/crypt/alg-sha512.h new file mode 100644 index 0000000000..93b6109fae --- /dev/null +++ b/lib/crypt/alg-sha512.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2020 Steffen Jaeckel */ + +#ifndef USE_HOSTCC +#include "common.h" +#else +#include +#endif + +#include "u-boot/sha512.h" + +#define INCLUDE_sha512crypt 1 + +#define SHA512_CTX sha512_context +#define SHA512_Init sha512_starts +#define SHA512_Update(c, i, l) sha512_update(c, (const void *)i, l) +#define SHA512_Final(b, c) sha512_finish(c, b) diff --git a/lib/crypt/crypt-port.h b/lib/crypt/crypt-port.h new file mode 100644 index 0000000000..680ffe9349 --- /dev/null +++ b/lib/crypt/crypt-port.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* Copyright (C) 2020 Steffen Jaeckel */ + +#include +#include + +#define NO_GENSALT +#define CRYPT_OUTPUT_SIZE 384 +#define ALG_SPECIFIC_SIZE 8192 + +#define ARG_UNUSED(x) (x) + +#define static_assert(a, b) _Static_assert(a, b) + +#define strtoul(cp, endp, base) simple_strtoul(cp, endp, base) + +extern const unsigned char ascii64[65]; + +#define b64t ((const char *)ascii64) + +void crypt_sha256crypt_rn(const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED(set_size), + uint8_t *output, size_t out_size, void *scratch, + size_t scr_size); +void crypt_sha512crypt_rn(const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED(set_size), + uint8_t *output, size_t out_size, void *scratch, + size_t scr_size); diff --git a/lib/crypt/crypt-sha256.c b/lib/crypt/crypt-sha256.c new file mode 100644 index 0000000000..37127d41e1 --- /dev/null +++ b/lib/crypt/crypt-sha256.c @@ -0,0 +1,313 @@ +/* One way encryption based on the SHA256-based Unix crypt implementation. + * + * Written by Ulrich Drepper in 2007 [1]. + * Modified by Zack Weinberg in 2017, 2018. + * Composed by Björn Esser in 2018. + * Modified by Björn Esser in 2020. + * Modified by Steffen Jaeckel in 2020. + * To the extent possible under law, the named authors have waived all + * copyright and related or neighboring rights to this work. + * + * See https://creativecommons.org/publicdomain/zero/1.0/ for further + * details. + * + * This file is a modified except from [2], lines 648 up to 909. + * + * [1] https://www.akkadia.org/drepper/sha-crypt.html + * [2] https://www.akkadia.org/drepper/SHA-crypt.txt + */ + +#include "crypt-port.h" +#include "alg-sha256.h" + +#include +#include +#include + +#if INCLUDE_sha256crypt + +/* Define our magic string to mark salt for SHA256 "encryption" + replacement. */ +static const char sha256_salt_prefix[] = "$5$"; + +/* Prefix for optional rounds specification. */ +static const char sha256_rounds_prefix[] = "rounds="; + +/* Maximum salt string length. */ +#define SALT_LEN_MAX 16 +/* Default number of rounds if not explicitly specified. */ +#define ROUNDS_DEFAULT 5000 +/* Minimum number of rounds. */ +#define ROUNDS_MIN 1000 +/* Maximum number of rounds. */ +#define ROUNDS_MAX 999999999 + +/* The maximum possible length of a SHA256-hashed password string, + including the terminating NUL character. Prefix (including its NUL) + + rounds tag ("rounds=$" = "rounds=\0") + strlen(ROUNDS_MAX) + + salt (up to SALT_LEN_MAX chars) + '$' + hash (43 chars). */ + +#define LENGTH_OF_NUMBER(n) (sizeof #n - 1) + +#define SHA256_HASH_LENGTH \ + (sizeof (sha256_salt_prefix) + sizeof (sha256_rounds_prefix) + \ + LENGTH_OF_NUMBER (ROUNDS_MAX) + SALT_LEN_MAX + 1 + 43) + +static_assert (SHA256_HASH_LENGTH <= CRYPT_OUTPUT_SIZE, + "CRYPT_OUTPUT_SIZE is too small for SHA256"); + +/* A sha256_buffer holds all of the sensitive intermediate data. */ +struct sha256_buffer +{ + SHA256_CTX ctx; + uint8_t result[32]; + uint8_t p_bytes[32]; + uint8_t s_bytes[32]; +}; + +static_assert (sizeof (struct sha256_buffer) <= ALG_SPECIFIC_SIZE, + "ALG_SPECIFIC_SIZE is too small for SHA256"); + + +/* Feed CTX with LEN bytes of a virtual byte sequence consisting of + BLOCK repeated over and over indefinitely. */ +static void +SHA256_Update_recycled (SHA256_CTX *ctx, + unsigned char block[32], size_t len) +{ + size_t cnt; + for (cnt = len; cnt >= 32; cnt -= 32) + SHA256_Update (ctx, block, 32); + SHA256_Update (ctx, block, cnt); +} + +void +crypt_sha256crypt_rn (const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED (set_size), + uint8_t *output, size_t out_size, + void *scratch, size_t scr_size) +{ + /* This shouldn't ever happen, but... */ + if (out_size < SHA256_HASH_LENGTH + || scr_size < sizeof (struct sha256_buffer)) + { + errno = ERANGE; + return; + } + + struct sha256_buffer *buf = scratch; + SHA256_CTX *ctx = &buf->ctx; + uint8_t *result = buf->result; + uint8_t *p_bytes = buf->p_bytes; + uint8_t *s_bytes = buf->s_bytes; + char *cp = (char *)output; + const char *salt = setting; + + size_t salt_size; + size_t cnt; + /* Default number of rounds. */ + size_t rounds = ROUNDS_DEFAULT; + bool rounds_custom = false; + + /* Find beginning of salt string. The prefix should normally always + be present. Just in case it is not. */ + if (strncmp (sha256_salt_prefix, salt, sizeof (sha256_salt_prefix) - 1) == 0) + /* Skip salt prefix. */ + salt += sizeof (sha256_salt_prefix) - 1; + + if (strncmp (salt, sha256_rounds_prefix, sizeof (sha256_rounds_prefix) - 1) + == 0) + { + const char *num = salt + sizeof (sha256_rounds_prefix) - 1; + /* Do not allow an explicit setting of zero rounds, nor of the + default number of rounds, nor leading zeroes on the rounds. */ + if (!(*num >= '1' && *num <= '9')) + { + errno = EINVAL; + return; + } + + errno = 0; + char *endp; + rounds = strtoul (num, &endp, 10); + if (endp == num || *endp != '$' + || rounds < ROUNDS_MIN + || rounds > ROUNDS_MAX + || errno) + { + errno = EINVAL; + return; + } + salt = endp + 1; + rounds_custom = true; + } + + /* The salt ends at the next '$' or the end of the string. + Ensure ':' does not appear in the salt (it is used as a separator in /etc/passwd). + Also check for '\n', as in /etc/passwd the whole parameters of the user data must + be on a single line. */ + salt_size = strcspn (salt, "$:\n"); + if (!(salt[salt_size] == '$' || !salt[salt_size])) + { + errno = EINVAL; + return; + } + + /* Ensure we do not use more salt than SALT_LEN_MAX. */ + if (salt_size > SALT_LEN_MAX) + salt_size = SALT_LEN_MAX; + + /* Compute alternate SHA256 sum with input PHRASE, SALT, and PHRASE. The + final result will be added to the first context. */ + SHA256_Init (ctx); + + /* Add phrase. */ + SHA256_Update (ctx, phrase, phr_size); + + /* Add salt. */ + SHA256_Update (ctx, salt, salt_size); + + /* Add phrase again. */ + SHA256_Update (ctx, phrase, phr_size); + + /* Now get result of this (32 bytes). */ + SHA256_Final (result, ctx); + + /* Prepare for the real work. */ + SHA256_Init (ctx); + + /* Add the phrase string. */ + SHA256_Update (ctx, phrase, phr_size); + + /* The last part is the salt string. This must be at most 8 + characters and it ends at the first `$' character (for + compatibility with existing implementations). */ + SHA256_Update (ctx, salt, salt_size); + + /* Add for any character in the phrase one byte of the alternate sum. */ + for (cnt = phr_size; cnt > 32; cnt -= 32) + SHA256_Update (ctx, result, 32); + SHA256_Update (ctx, result, cnt); + + /* Take the binary representation of the length of the phrase and for every + 1 add the alternate sum, for every 0 the phrase. */ + for (cnt = phr_size; cnt > 0; cnt >>= 1) + if ((cnt & 1) != 0) + SHA256_Update (ctx, result, 32); + else + SHA256_Update (ctx, phrase, phr_size); + + /* Create intermediate result. */ + SHA256_Final (result, ctx); + + /* Start computation of P byte sequence. */ + SHA256_Init (ctx); + + /* For every character in the password add the entire password. */ + for (cnt = 0; cnt < phr_size; ++cnt) + SHA256_Update (ctx, phrase, phr_size); + + /* Finish the digest. */ + SHA256_Final (p_bytes, ctx); + + /* Start computation of S byte sequence. */ + SHA256_Init (ctx); + + /* For every character in the password add the entire password. */ + for (cnt = 0; cnt < (size_t) 16 + (size_t) result[0]; ++cnt) + SHA256_Update (ctx, salt, salt_size); + + /* Finish the digest. */ + SHA256_Final (s_bytes, ctx); + + /* Repeatedly run the collected hash value through SHA256 to burn + CPU cycles. */ + for (cnt = 0; cnt < rounds; ++cnt) + { + /* New context. */ + SHA256_Init (ctx); + + /* Add phrase or last result. */ + if ((cnt & 1) != 0) + SHA256_Update_recycled (ctx, p_bytes, phr_size); + else + SHA256_Update (ctx, result, 32); + + /* Add salt for numbers not divisible by 3. */ + if (cnt % 3 != 0) + SHA256_Update_recycled (ctx, s_bytes, salt_size); + + /* Add phrase for numbers not divisible by 7. */ + if (cnt % 7 != 0) + SHA256_Update_recycled (ctx, p_bytes, phr_size); + + /* Add phrase or last result. */ + if ((cnt & 1) != 0) + SHA256_Update (ctx, result, 32); + else + SHA256_Update_recycled (ctx, p_bytes, phr_size); + + /* Create intermediate result. */ + SHA256_Final (result, ctx); + } + + /* Now we can construct the result string. It consists of four + parts, one of which is optional. We already know that there + is sufficient space at CP for the longest possible result string. */ + memcpy (cp, sha256_salt_prefix, sizeof (sha256_salt_prefix) - 1); + cp += sizeof (sha256_salt_prefix) - 1; + + if (rounds_custom) + { + int n = snprintf (cp, + SHA256_HASH_LENGTH - (sizeof (sha256_salt_prefix) - 1), + "%s%zu$", sha256_rounds_prefix, rounds); + cp += n; + } + + memcpy (cp, salt, salt_size); + cp += salt_size; + *cp++ = '$'; + +#define b64_from_24bit(B2, B1, B0, N) \ + do { \ + unsigned int w = ((((unsigned int)(B2)) << 16) | \ + (((unsigned int)(B1)) << 8) | \ + ((unsigned int)(B0))); \ + int n = (N); \ + while (n-- > 0) \ + { \ + *cp++ = b64t[w & 0x3f]; \ + w >>= 6; \ + } \ + } while (0) + + b64_from_24bit (result[0], result[10], result[20], 4); + b64_from_24bit (result[21], result[1], result[11], 4); + b64_from_24bit (result[12], result[22], result[2], 4); + b64_from_24bit (result[3], result[13], result[23], 4); + b64_from_24bit (result[24], result[4], result[14], 4); + b64_from_24bit (result[15], result[25], result[5], 4); + b64_from_24bit (result[6], result[16], result[26], 4); + b64_from_24bit (result[27], result[7], result[17], 4); + b64_from_24bit (result[18], result[28], result[8], 4); + b64_from_24bit (result[9], result[19], result[29], 4); + b64_from_24bit (0, result[31], result[30], 3); + + *cp = '\0'; +} + +#ifndef NO_GENSALT + +void +gensalt_sha256crypt_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t output_size) +{ + gensalt_sha_rn ('5', SALT_LEN_MAX, ROUNDS_DEFAULT, ROUNDS_MIN, ROUNDS_MAX, + count, rbytes, nrbytes, output, output_size); +} + +#endif + +#endif diff --git a/lib/crypt/crypt-sha512.c b/lib/crypt/crypt-sha512.c new file mode 100644 index 0000000000..3616019445 --- /dev/null +++ b/lib/crypt/crypt-sha512.c @@ -0,0 +1,328 @@ +/* One way encryption based on the SHA512-based Unix crypt implementation. + * + * Written by Ulrich Drepper in 2007 [1]. + * Modified by Zack Weinberg in 2017, 2018. + * Composed by Björn Esser in 2018. + * Modified by Björn Esser in 2020. + * Modified by Steffen Jaeckel in 2020. + * To the extent possible under law, the named authors have waived all + * copyright and related or neighboring rights to this work. + * + * See https://creativecommons.org/publicdomain/zero/1.0/ for further + * details. + * + * This file is a modified except from [2], lines 1403 up to 1676. + * + * [1] https://www.akkadia.org/drepper/sha-crypt.html + * [2] https://www.akkadia.org/drepper/SHA-crypt.txt + */ + +#include "crypt-port.h" +#include "alg-sha512.h" + +#include +#include +#include + +#if INCLUDE_sha512crypt + +/* Define our magic string to mark salt for SHA512 "encryption" + replacement. */ +static const char sha512_salt_prefix[] = "$6$"; + +/* Prefix for optional rounds specification. */ +static const char sha512_rounds_prefix[] = "rounds="; + +/* Maximum salt string length. */ +#define SALT_LEN_MAX 16 +/* Default number of rounds if not explicitly specified. */ +#define ROUNDS_DEFAULT 5000 +/* Minimum number of rounds. */ +#define ROUNDS_MIN 1000 +/* Maximum number of rounds. */ +#define ROUNDS_MAX 999999999 + +/* The maximum possible length of a SHA512-hashed password string, + including the terminating NUL character. Prefix (including its NUL) + + rounds tag ("rounds=$" = "rounds=\0") + strlen(ROUNDS_MAX) + + salt (up to SALT_LEN_MAX chars) + '$' + hash (86 chars). */ + +#define LENGTH_OF_NUMBER(n) (sizeof #n - 1) + +#define SHA512_HASH_LENGTH \ + (sizeof (sha512_salt_prefix) + sizeof (sha512_rounds_prefix) + \ + LENGTH_OF_NUMBER (ROUNDS_MAX) + SALT_LEN_MAX + 1 + 86) + +static_assert (SHA512_HASH_LENGTH <= CRYPT_OUTPUT_SIZE, + "CRYPT_OUTPUT_SIZE is too small for SHA512"); + +/* A sha512_buffer holds all of the sensitive intermediate data. */ +struct sha512_buffer +{ + SHA512_CTX ctx; + uint8_t result[64]; + uint8_t p_bytes[64]; + uint8_t s_bytes[64]; +}; + +static_assert (sizeof (struct sha512_buffer) <= ALG_SPECIFIC_SIZE, + "ALG_SPECIFIC_SIZE is too small for SHA512"); + + +/* Subroutine of _xcrypt_crypt_sha512crypt_rn: Feed CTX with LEN bytes of a + virtual byte sequence consisting of BLOCK repeated over and over + indefinitely. */ +static void +sha512_process_recycled_bytes (unsigned char block[64], size_t len, + SHA512_CTX *ctx) +{ + size_t cnt; + for (cnt = len; cnt >= 64; cnt -= 64) + SHA512_Update (ctx, block, 64); + SHA512_Update (ctx, block, cnt); +} + +void +crypt_sha512crypt_rn (const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED (set_size), + uint8_t *output, size_t out_size, + void *scratch, size_t scr_size) +{ + /* This shouldn't ever happen, but... */ + if (out_size < SHA512_HASH_LENGTH + || scr_size < sizeof (struct sha512_buffer)) + { + errno = ERANGE; + return; + } + + struct sha512_buffer *buf = scratch; + SHA512_CTX *ctx = &buf->ctx; + uint8_t *result = buf->result; + uint8_t *p_bytes = buf->p_bytes; + uint8_t *s_bytes = buf->s_bytes; + char *cp = (char *)output; + const char *salt = setting; + + size_t salt_size; + size_t cnt; + /* Default number of rounds. */ + size_t rounds = ROUNDS_DEFAULT; + bool rounds_custom = false; + + /* Find beginning of salt string. The prefix should normally always + be present. Just in case it is not. */ + if (strncmp (sha512_salt_prefix, salt, sizeof (sha512_salt_prefix) - 1) == 0) + /* Skip salt prefix. */ + salt += sizeof (sha512_salt_prefix) - 1; + + if (strncmp (salt, sha512_rounds_prefix, sizeof (sha512_rounds_prefix) - 1) + == 0) + { + const char *num = salt + sizeof (sha512_rounds_prefix) - 1; + /* Do not allow an explicit setting of zero rounds, nor of the + default number of rounds, nor leading zeroes on the rounds. */ + if (!(*num >= '1' && *num <= '9')) + { + errno = EINVAL; + return; + } + + errno = 0; + char *endp; + rounds = strtoul (num, &endp, 10); + if (endp == num || *endp != '$' + || rounds < ROUNDS_MIN + || rounds > ROUNDS_MAX + || errno) + { + errno = EINVAL; + return; + } + salt = endp + 1; + rounds_custom = true; + } + + /* The salt ends at the next '$' or the end of the string. + Ensure ':' does not appear in the salt (it is used as a separator in /etc/passwd). + Also check for '\n', as in /etc/passwd the whole parameters of the user data must + be on a single line. */ + salt_size = strcspn (salt, "$:\n"); + if (!(salt[salt_size] == '$' || !salt[salt_size])) + { + errno = EINVAL; + return; + } + + /* Ensure we do not use more salt than SALT_LEN_MAX. */ + if (salt_size > SALT_LEN_MAX) + salt_size = SALT_LEN_MAX; + + /* Compute alternate SHA512 sum with input PHRASE, SALT, and PHRASE. The + final result will be added to the first context. */ + SHA512_Init (ctx); + + /* Add phrase. */ + SHA512_Update (ctx, phrase, phr_size); + + /* Add salt. */ + SHA512_Update (ctx, salt, salt_size); + + /* Add phrase again. */ + SHA512_Update (ctx, phrase, phr_size); + + /* Now get result of this (64 bytes) and add it to the other + context. */ + SHA512_Final (result, ctx); + + /* Prepare for the real work. */ + SHA512_Init (ctx); + + /* Add the phrase string. */ + SHA512_Update (ctx, phrase, phr_size); + + /* The last part is the salt string. This must be at most 8 + characters and it ends at the first `$' character (for + compatibility with existing implementations). */ + SHA512_Update (ctx, salt, salt_size); + + /* Add for any character in the phrase one byte of the alternate sum. */ + for (cnt = phr_size; cnt > 64; cnt -= 64) + SHA512_Update (ctx, result, 64); + SHA512_Update (ctx, result, cnt); + + /* Take the binary representation of the length of the phrase and for every + 1 add the alternate sum, for every 0 the phrase. */ + for (cnt = phr_size; cnt > 0; cnt >>= 1) + if ((cnt & 1) != 0) + SHA512_Update (ctx, result, 64); + else + SHA512_Update (ctx, phrase, phr_size); + + /* Create intermediate result. */ + SHA512_Final (result, ctx); + + /* Start computation of P byte sequence. */ + SHA512_Init (ctx); + + /* For every character in the password add the entire password. */ + for (cnt = 0; cnt < phr_size; ++cnt) + SHA512_Update (ctx, phrase, phr_size); + + /* Finish the digest. */ + SHA512_Final (p_bytes, ctx); + + /* Start computation of S byte sequence. */ + SHA512_Init (ctx); + + /* For every character in the password add the entire password. */ + for (cnt = 0; cnt < (size_t) 16 + (size_t) result[0]; ++cnt) + SHA512_Update (ctx, salt, salt_size); + + /* Finish the digest. */ + SHA512_Final (s_bytes, ctx); + + /* Repeatedly run the collected hash value through SHA512 to burn + CPU cycles. */ + for (cnt = 0; cnt < rounds; ++cnt) + { + /* New context. */ + SHA512_Init (ctx); + + /* Add phrase or last result. */ + if ((cnt & 1) != 0) + sha512_process_recycled_bytes (p_bytes, phr_size, ctx); + else + SHA512_Update (ctx, result, 64); + + /* Add salt for numbers not divisible by 3. */ + if (cnt % 3 != 0) + sha512_process_recycled_bytes (s_bytes, salt_size, ctx); + + /* Add phrase for numbers not divisible by 7. */ + if (cnt % 7 != 0) + sha512_process_recycled_bytes (p_bytes, phr_size, ctx); + + /* Add phrase or last result. */ + if ((cnt & 1) != 0) + SHA512_Update (ctx, result, 64); + else + sha512_process_recycled_bytes (p_bytes, phr_size, ctx); + + /* Create intermediate result. */ + SHA512_Final (result, ctx); + } + + /* Now we can construct the result string. It consists of four + parts, one of which is optional. We already know that buflen is + at least sha512_hash_length, therefore none of the string bashing + below can overflow the buffer. */ + + memcpy (cp, sha512_salt_prefix, sizeof (sha512_salt_prefix) - 1); + cp += sizeof (sha512_salt_prefix) - 1; + + if (rounds_custom) + { + int n = snprintf (cp, + SHA512_HASH_LENGTH - (sizeof (sha512_salt_prefix) - 1), + "%s%zu$", sha512_rounds_prefix, rounds); + cp += n; + } + + memcpy (cp, salt, salt_size); + cp += salt_size; + *cp++ = '$'; + +#define b64_from_24bit(B2, B1, B0, N) \ + do { \ + unsigned int w = ((((unsigned int)(B2)) << 16) | \ + (((unsigned int)(B1)) << 8) | \ + ((unsigned int)(B0))); \ + int n = (N); \ + while (n-- > 0) \ + { \ + *cp++ = b64t[w & 0x3f]; \ + w >>= 6; \ + } \ + } while (0) + + b64_from_24bit (result[0], result[21], result[42], 4); + b64_from_24bit (result[22], result[43], result[1], 4); + b64_from_24bit (result[44], result[2], result[23], 4); + b64_from_24bit (result[3], result[24], result[45], 4); + b64_from_24bit (result[25], result[46], result[4], 4); + b64_from_24bit (result[47], result[5], result[26], 4); + b64_from_24bit (result[6], result[27], result[48], 4); + b64_from_24bit (result[28], result[49], result[7], 4); + b64_from_24bit (result[50], result[8], result[29], 4); + b64_from_24bit (result[9], result[30], result[51], 4); + b64_from_24bit (result[31], result[52], result[10], 4); + b64_from_24bit (result[53], result[11], result[32], 4); + b64_from_24bit (result[12], result[33], result[54], 4); + b64_from_24bit (result[34], result[55], result[13], 4); + b64_from_24bit (result[56], result[14], result[35], 4); + b64_from_24bit (result[15], result[36], result[57], 4); + b64_from_24bit (result[37], result[58], result[16], 4); + b64_from_24bit (result[59], result[17], result[38], 4); + b64_from_24bit (result[18], result[39], result[60], 4); + b64_from_24bit (result[40], result[61], result[19], 4); + b64_from_24bit (result[62], result[20], result[41], 4); + b64_from_24bit (0, 0, result[63], 2); + + *cp = '\0'; +} + +#ifndef NO_GENSALT + +void +gensalt_sha512crypt_rn (unsigned long count, + const uint8_t *rbytes, size_t nrbytes, + uint8_t *output, size_t output_size) +{ + gensalt_sha_rn ('6', SALT_LEN_MAX, ROUNDS_DEFAULT, ROUNDS_MIN, ROUNDS_MAX, + count, rbytes, nrbytes, output, output_size); +} + +#endif + +#endif diff --git a/lib/crypt/crypt.c b/lib/crypt/crypt.c new file mode 100644 index 0000000000..4ec6079768 --- /dev/null +++ b/lib/crypt/crypt.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (C) 2020 Steffen Jaeckel */ + +#include +#include +#include "crypt-port.h" + +typedef void (*crypt_fn)(const char *, size_t, const char *, size_t, uint8_t *, + size_t, void *, size_t); + +const unsigned char ascii64[65] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void equals_constant_time(const void *a_, const void *b_, size_t len, + int *equal) +{ + u8 ret = 0; + const u8 *a = a_, *b = b_; + int i; + + for (i = 0; i < len; i++) + ret |= a[i] ^ b[i]; + + ret |= ret >> 4; + ret |= ret >> 2; + ret |= ret >> 1; + ret &= 1; + + *equal = ret ^ 1; +} + +void crypt_compare(const char *should, const char *passphrase, int *equal) +{ + u8 output[CRYPT_OUTPUT_SIZE], scratch[ALG_SPECIFIC_SIZE]; + size_t n; + struct { + const char *prefix; + crypt_fn crypt; + } crypt_algos[] = { +#if defined(CONFIG_CRYPT_PW_SHA256) + { "$5$", crypt_sha256crypt_rn }, +#endif +#if defined(CONFIG_CRYPT_PW_SHA512) + { "$6$", crypt_sha512crypt_rn }, +#endif + { NULL, NULL } + }; + + *equal = 0; + + for (n = 0; n < ARRAY_SIZE(crypt_algos); ++n) { + if (!crypt_algos[n].prefix) + continue; + if (strncmp(should, crypt_algos[n].prefix, 3) == 0) + break; + } + + if (n >= ARRAY_SIZE(crypt_algos)) + return; + + crypt_algos[n].crypt(passphrase, strlen(passphrase), should, 0, output, + sizeof(output), scratch, sizeof(scratch)); + + /* early return on error, nothing really happened inside the crypt() function */ + if (errno == ERANGE || errno == EINVAL) + return; + + equals_constant_time(should, output, strlen((const char *)output), + equal); + + memset(scratch, 0, sizeof(scratch)); + memset(output, 0, sizeof(output)); +} diff --git a/test/Kconfig b/test/Kconfig index ab3ac54a1b..e15ba239eb 100644 --- a/test/Kconfig +++ b/test/Kconfig @@ -38,6 +38,16 @@ config UT_LIB_ASN1 Enables a test which exercises asn1 compiler and decoder function via various parsers. +config UT_LIB_CRYPT + bool "Unit test for crypt-style password hashing" + depends on !SPL && AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION + default y + select CRYPT_PW + select CRYPT_PW_SHA256 + select CRYPT_PW_SHA512 + help + Enables a test for the crypt-style password hash functions. + config UT_LIB_RSA bool "Unit test for rsa_verify() function" depends on RSA diff --git a/test/lib/Makefile b/test/lib/Makefile index aa2e66bc7f..6fd0514251 100644 --- a/test/lib/Makefile +++ b/test/lib/Makefile @@ -17,3 +17,4 @@ obj-$(CONFIG_UT_LIB_ASN1) += asn1.o obj-$(CONFIG_UT_LIB_RSA) += rsa.o obj-$(CONFIG_AES) += test_aes.o obj-$(CONFIG_GETOPT) += getopt.o +obj-$(CONFIG_UT_LIB_CRYPT) += test_crypt.o diff --git a/test/lib/test_crypt.c b/test/lib/test_crypt.c new file mode 100644 index 0000000000..277e4efed1 --- /dev/null +++ b/test/lib/test_crypt.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021 Steffen Jaeckel + * + * Unit test for crypt-style password hashing + */ + +#include +#include +#include +#include + +#include + +/** + * lib_crypt() - unit test for crypt-style password hashing + * + * @uts: unit test state + * Return: 0 = success, 1 = failure + */ +static int lib_crypt(struct unit_test_state *uts) +{ + int equals = 0; + + if (IS_ENABLED(CONFIG_CRYPT_PW_SHA256)) { + crypt_compare( + "$5$rounds=640000$TM4lL4zXDG7F4aRX$JM7a9wmvodnA0WasjTztj6mxg.KVuk6doQ/eBhdcapB", + "password", &equals); + ut_assertf(equals == 1, + "crypt-sha256 password hash didn't match\n"); + } + equals = 0; + if (IS_ENABLED(CONFIG_CRYPT_PW_SHA512)) { + crypt_compare( + "$6$rounds=640000$fCTP1F0N5JLq2eND$z5EzK5KZJA9JnOaj5d1Gg/2v6VqFOQJ3bVekWuCPauabutBt/8qzV1exJnytUyhbq3H0bSBXtodwNbtGEi/Tm/", + "password", &equals); + ut_assertf(equals == 1, + "crypt-sha512 password hash didn't match\n"); + } + + return CMD_RET_SUCCESS; +} + +LIB_TEST(lib_crypt, 0); From 29bbe71ccfef3440b4881259c6f8e39b6e7924c6 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:34 +0200 Subject: [PATCH 05/11] lib: wrap crypt API to hide errno usage In order to prevent using the global errno, replace it with a static version and create a wrapper function which returns the error value. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher --- include/crypt.h | 3 ++- lib/crypt/alg-sha256.h | 6 ------ lib/crypt/alg-sha512.h | 6 ------ lib/crypt/crypt-port.h | 18 ++++++++++-------- lib/crypt/crypt-sha256.c | 27 +++++++++++++++++++++++++-- lib/crypt/crypt-sha512.c | 27 +++++++++++++++++++++++++-- lib/crypt/crypt.c | 25 ++++++++++++++----------- test/lib/test_crypt.c | 24 ++++++++++++++++++++++-- 8 files changed, 98 insertions(+), 38 deletions(-) diff --git a/include/crypt.h b/include/crypt.h index e0be2832ff..f18a1705d4 100644 --- a/include/crypt.h +++ b/include/crypt.h @@ -9,5 +9,6 @@ * @equal Pointer to an int where the result is stored * '0' = unequal * '1' = equal + * @return 0 on success, error code of errno else */ -void crypt_compare(const char *should, const char *passphrase, int *equal); +int crypt_compare(const char *should, const char *passphrase, int *equal); diff --git a/lib/crypt/alg-sha256.h b/lib/crypt/alg-sha256.h index e4b29c9f31..62e7b9d5c0 100644 --- a/lib/crypt/alg-sha256.h +++ b/lib/crypt/alg-sha256.h @@ -1,12 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* Copyright (C) 2020 Steffen Jaeckel */ -#ifndef USE_HOSTCC -#include "common.h" -#else -#include -#endif - #include "u-boot/sha256.h" #define INCLUDE_sha256crypt 1 diff --git a/lib/crypt/alg-sha512.h b/lib/crypt/alg-sha512.h index 93b6109fae..47e45730cc 100644 --- a/lib/crypt/alg-sha512.h +++ b/lib/crypt/alg-sha512.h @@ -1,12 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0+ */ /* Copyright (C) 2020 Steffen Jaeckel */ -#ifndef USE_HOSTCC -#include "common.h" -#else -#include -#endif - #include "u-boot/sha512.h" #define INCLUDE_sha512crypt 1 diff --git a/lib/crypt/crypt-port.h b/lib/crypt/crypt-port.h index 680ffe9349..6b9542d75b 100644 --- a/lib/crypt/crypt-port.h +++ b/lib/crypt/crypt-port.h @@ -18,11 +18,13 @@ extern const unsigned char ascii64[65]; #define b64t ((const char *)ascii64) -void crypt_sha256crypt_rn(const char *phrase, size_t phr_size, - const char *setting, size_t ARG_UNUSED(set_size), - uint8_t *output, size_t out_size, void *scratch, - size_t scr_size); -void crypt_sha512crypt_rn(const char *phrase, size_t phr_size, - const char *setting, size_t ARG_UNUSED(set_size), - uint8_t *output, size_t out_size, void *scratch, - size_t scr_size); +int crypt_sha256crypt_rn_wrapped(const char *phrase, size_t phr_size, + const char *setting, + size_t ARG_UNUSED(set_size), uint8_t *output, + size_t out_size, void *scratch, + size_t scr_size); +int crypt_sha512crypt_rn_wrapped(const char *phrase, size_t phr_size, + const char *setting, + size_t ARG_UNUSED(set_size), uint8_t *output, + size_t out_size, void *scratch, + size_t scr_size); diff --git a/lib/crypt/crypt-sha256.c b/lib/crypt/crypt-sha256.c index 37127d41e1..335c8880d8 100644 --- a/lib/crypt/crypt-sha256.c +++ b/lib/crypt/crypt-sha256.c @@ -1,10 +1,14 @@ +// SPDX-License-Identifier: CC0-1.0 +/* Based on libxcrypt v4.4.17-0-g6b110bc */ /* One way encryption based on the SHA256-based Unix crypt implementation. * * Written by Ulrich Drepper in 2007 [1]. * Modified by Zack Weinberg in 2017, 2018. * Composed by Björn Esser in 2018. * Modified by Björn Esser in 2020. - * Modified by Steffen Jaeckel in 2020. + * Modified by Steffen Jaeckel in 2021 + * for U-Boot, instead of using the global errno to use a static one + * inside this file. * To the extent possible under law, the named authors have waived all * copyright and related or neighboring rights to this work. * @@ -20,7 +24,7 @@ #include "crypt-port.h" #include "alg-sha256.h" -#include +#include #include #include @@ -69,6 +73,25 @@ static_assert (sizeof (struct sha256_buffer) <= ALG_SPECIFIC_SIZE, "ALG_SPECIFIC_SIZE is too small for SHA256"); +/* Use this instead of including errno.h */ +static int errno; + +void crypt_sha256crypt_rn(const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED(set_size), + uint8_t *output, size_t out_size, void *scratch, + size_t scr_size); + +int crypt_sha256crypt_rn_wrapped(const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + u8 *output, size_t out_size, void *scratch, + size_t scr_size) +{ + errno = 0; + crypt_sha256crypt_rn(phrase, phr_size, setting, set_size, output, + out_size, scratch, scr_size); + return -errno; +} + /* Feed CTX with LEN bytes of a virtual byte sequence consisting of BLOCK repeated over and over indefinitely. */ static void diff --git a/lib/crypt/crypt-sha512.c b/lib/crypt/crypt-sha512.c index 3616019445..8c8e6dd3de 100644 --- a/lib/crypt/crypt-sha512.c +++ b/lib/crypt/crypt-sha512.c @@ -1,10 +1,14 @@ +// SPDX-License-Identifier: CC0-1.0 +/* Based on libxcrypt v4.4.17-0-g6b110bc */ /* One way encryption based on the SHA512-based Unix crypt implementation. * * Written by Ulrich Drepper in 2007 [1]. * Modified by Zack Weinberg in 2017, 2018. * Composed by Björn Esser in 2018. * Modified by Björn Esser in 2020. - * Modified by Steffen Jaeckel in 2020. + * Modified by Steffen Jaeckel in 2021 + * for U-Boot, instead of using the global errno to use a static one + * inside this file. * To the extent possible under law, the named authors have waived all * copyright and related or neighboring rights to this work. * @@ -20,7 +24,7 @@ #include "crypt-port.h" #include "alg-sha512.h" -#include +#include #include #include @@ -69,6 +73,25 @@ static_assert (sizeof (struct sha512_buffer) <= ALG_SPECIFIC_SIZE, "ALG_SPECIFIC_SIZE is too small for SHA512"); +/* Use this instead of including errno.h */ +static int errno; + +void crypt_sha512crypt_rn(const char *phrase, size_t phr_size, + const char *setting, size_t ARG_UNUSED(set_size), + uint8_t *output, size_t out_size, void *scratch, + size_t scr_size); + +int crypt_sha512crypt_rn_wrapped(const char *phrase, size_t phr_size, + const char *setting, size_t set_size, + u8 *output, size_t out_size, void *scratch, + size_t scr_size) +{ + errno = 0; + crypt_sha512crypt_rn(phrase, phr_size, setting, set_size, output, + out_size, scratch, scr_size); + return -errno; +} + /* Subroutine of _xcrypt_crypt_sha512crypt_rn: Feed CTX with LEN bytes of a virtual byte sequence consisting of BLOCK repeated over and over indefinitely. */ diff --git a/lib/crypt/crypt.c b/lib/crypt/crypt.c index 4ec6079768..247c34b2a9 100644 --- a/lib/crypt/crypt.c +++ b/lib/crypt/crypt.c @@ -5,8 +5,8 @@ #include #include "crypt-port.h" -typedef void (*crypt_fn)(const char *, size_t, const char *, size_t, uint8_t *, - size_t, void *, size_t); +typedef int (*crypt_fn)(const char *, size_t, const char *, size_t, uint8_t *, + size_t, void *, size_t); const unsigned char ascii64[65] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -29,19 +29,20 @@ static void equals_constant_time(const void *a_, const void *b_, size_t len, *equal = ret ^ 1; } -void crypt_compare(const char *should, const char *passphrase, int *equal) +int crypt_compare(const char *should, const char *passphrase, int *equal) { u8 output[CRYPT_OUTPUT_SIZE], scratch[ALG_SPECIFIC_SIZE]; size_t n; + int err; struct { const char *prefix; crypt_fn crypt; } crypt_algos[] = { #if defined(CONFIG_CRYPT_PW_SHA256) - { "$5$", crypt_sha256crypt_rn }, + { "$5$", crypt_sha256crypt_rn_wrapped }, #endif #if defined(CONFIG_CRYPT_PW_SHA512) - { "$6$", crypt_sha512crypt_rn }, + { "$6$", crypt_sha512crypt_rn_wrapped }, #endif { NULL, NULL } }; @@ -56,18 +57,20 @@ void crypt_compare(const char *should, const char *passphrase, int *equal) } if (n >= ARRAY_SIZE(crypt_algos)) - return; - - crypt_algos[n].crypt(passphrase, strlen(passphrase), should, 0, output, - sizeof(output), scratch, sizeof(scratch)); + return -EINVAL; + err = crypt_algos[n].crypt(passphrase, strlen(passphrase), should, 0, + output, sizeof(output), scratch, + sizeof(scratch)); /* early return on error, nothing really happened inside the crypt() function */ - if (errno == ERANGE || errno == EINVAL) - return; + if (err) + return err; equals_constant_time(should, output, strlen((const char *)output), equal); memset(scratch, 0, sizeof(scratch)); memset(output, 0, sizeof(output)); + + return 0; } diff --git a/test/lib/test_crypt.c b/test/lib/test_crypt.c index 277e4efed1..fb21edf974 100644 --- a/test/lib/test_crypt.c +++ b/test/lib/test_crypt.c @@ -21,19 +21,39 @@ static int lib_crypt(struct unit_test_state *uts) { int equals = 0; + int err; + + err = crypt_compare("", "password", &equals); + ut_assertf(err != 0, "crypt_compare successful but should not\n"); + ut_assertf(equals != 1, + "crypt_compare password hash matched but should not\n"); if (IS_ENABLED(CONFIG_CRYPT_PW_SHA256)) { - crypt_compare( + err = crypt_compare("$5$", "password", &equals); + ut_assertf(err == 0, "crypt-sha256 not successful\n"); + ut_assertf( + equals != 1, + "crypt-sha256 password hash matched but should not\n"); + + err = crypt_compare( "$5$rounds=640000$TM4lL4zXDG7F4aRX$JM7a9wmvodnA0WasjTztj6mxg.KVuk6doQ/eBhdcapB", "password", &equals); + ut_assertf(err == 0, "crypt-sha256 failed: %d\n", err); ut_assertf(equals == 1, "crypt-sha256 password hash didn't match\n"); } equals = 0; if (IS_ENABLED(CONFIG_CRYPT_PW_SHA512)) { - crypt_compare( + err = crypt_compare("$6$", "password", &equals); + ut_assertf(err == 0, "crypt-sha512 not successful\n"); + ut_assertf( + equals != 1, + "crypt-sha512 password hash matched but should not\n"); + + err = crypt_compare( "$6$rounds=640000$fCTP1F0N5JLq2eND$z5EzK5KZJA9JnOaj5d1Gg/2v6VqFOQJ3bVekWuCPauabutBt/8qzV1exJnytUyhbq3H0bSBXtodwNbtGEi/Tm/", "password", &equals); + ut_assertf(err == 0, "crypt-sha512 failed: %d\n", err); ut_assertf(equals == 1, "crypt-sha512 password hash didn't match\n"); } From 1a4a778666842f22752c3af93f5cd8b94948cb9e Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:35 +0200 Subject: [PATCH 06/11] common: integrate crypt-based passwords Hook into the autoboot flow as an alternative to the existing mechanisms. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher --- common/Kconfig.boot | 38 ++++++++++++++++++--- common/autoboot.c | 81 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 14 deletions(-) diff --git a/common/Kconfig.boot b/common/Kconfig.boot index b4dc49e019..fe60ad0171 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -812,10 +812,17 @@ config AUTOBOOT_ENCRYPTION depends on AUTOBOOT_KEYED help This option allows a string to be entered into U-Boot to stop the - autoboot. The string itself is hashed and compared against the hash - in the environment variable 'bootstopkeysha256'. If it matches then - boot stops and a command-line prompt is presented. - + autoboot. + The behavior depends whether CONFIG_CRYPT_PW from lib is enabled + or not. + In case CONFIG_CRYPT_PW is enabled, the string will be forwarded + to the crypt-based functionality and be compared against the + string in the environment variable 'bootstopkeycrypt'. + In case CONFIG_CRYPT_PW is disabled the string itself is hashed + and compared against the hash in the environment variable + 'bootstopkeysha256'. + If it matches in either case then boot stops and + a command-line prompt is presented. This provides a way to ship a secure production device which can also be accessed at the U-Boot command line. @@ -853,9 +860,30 @@ config AUTOBOOT_KEYED_CTRLC Setting this variable provides an escape sequence from the limited "password" strings. +config AUTOBOOT_STOP_STR_ENABLE + bool "Enable fixed string to stop autobooting" + depends on AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION + help + This option enables the feature to add a fixed stop + string that is defined at compile time. + In every case it will be tried to load the stop + string from the environment. + In case this is enabled and there is no stop string + in the environment, this will be used as default value. + +config AUTOBOOT_STOP_STR_CRYPT + string "Stop autobooting via crypt-hashed password" + depends on AUTOBOOT_STOP_STR_ENABLE && CRYPT_PW + help + This option adds the feature to only stop the autobooting, + and therefore boot into the U-Boot prompt, when the input + string / password matches a values that is hashed via + one of the supported crypt-style password hashing options + and saved in the environment variable "bootstopkeycrypt". + config AUTOBOOT_STOP_STR_SHA256 string "Stop autobooting via SHA256 encrypted password" - depends on AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION + depends on AUTOBOOT_STOP_STR_ENABLE help This option adds the feature to only stop the autobooting, and therefore boot into the U-Boot prompt, when the input diff --git a/common/autoboot.c b/common/autoboot.c index b42148c729..107fb1533d 100644 --- a/common/autoboot.c +++ b/common/autoboot.c @@ -23,6 +23,7 @@ #include #include #include +#include DECLARE_GLOBAL_DATA_PTR; @@ -38,10 +39,11 @@ DECLARE_GLOBAL_DATA_PTR; static int stored_bootdelay; static int menukey; -#ifdef CONFIG_AUTOBOOT_ENCRYPTION -#define AUTOBOOT_STOP_STR_SHA256 CONFIG_AUTOBOOT_STOP_STR_SHA256 -#else -#define AUTOBOOT_STOP_STR_SHA256 "" +#if !defined(CONFIG_AUTOBOOT_STOP_STR_CRYPT) +#define CONFIG_AUTOBOOT_STOP_STR_CRYPT "" +#endif +#if !defined(CONFIG_AUTOBOOT_STOP_STR_SHA256) +#define CONFIG_AUTOBOOT_STOP_STR_SHA256 "" #endif #ifdef CONFIG_AUTOBOOT_USE_MENUKEY @@ -50,6 +52,63 @@ static int menukey; #define AUTOBOOT_MENUKEY 0 #endif +/** + * passwd_abort_crypt() - check for a crypt-style hashed key sequence to abort booting + * + * This checks for the user entering a password within a given time. + * + * The entered password is hashed via one of the crypt-style hash methods + * and compared to the pre-defined value from either + * the environment variable "bootstopkeycrypt" + * or + * the config value CONFIG_AUTOBOOT_STOP_STR_CRYPT + * + * @etime: Timeout value ticks (stop when get_ticks() reachs this) + * @return 0 if autoboot should continue, 1 if it should stop + */ +static int passwd_abort_crypt(uint64_t etime) +{ + const char *crypt_env_str = env_get("bootstopkeycrypt"); + char presskey[MAX_DELAY_STOP_STR]; + u_int presskey_len = 0; + int abort = 0; + int err; + + if (IS_ENABLED(CONFIG_AUTOBOOT_STOP_STR_ENABLE) && !crypt_env_str) + crypt_env_str = CONFIG_AUTOBOOT_STOP_STR_CRYPT; + + if (!crypt_env_str) + return 0; + + /* We expect the stop-string to be newline-terminated */ + do { + if (tstc()) { + /* Check for input string overflow */ + if (presskey_len >= sizeof(presskey)) + return 0; + + presskey[presskey_len] = getchar(); + + if ((presskey[presskey_len] == '\r') || + (presskey[presskey_len] == '\n')) { + presskey[presskey_len] = '\0'; + err = crypt_compare(crypt_env_str, presskey, + &abort); + if (err) + debug_bootkeys( + "crypt_compare() failed with: %s\n", + errno_str(err)); + /* you had one chance */ + break; + } else { + presskey_len++; + } + } + } while (get_ticks() <= etime); + + return abort; +} + /* * Use a "constant-length" time compare function for this * hash compare: @@ -89,7 +148,7 @@ static int passwd_abort_sha256(uint64_t etime) int ret; if (sha_env_str == NULL) - sha_env_str = AUTOBOOT_STOP_STR_SHA256; + sha_env_str = CONFIG_AUTOBOOT_STOP_STR_SHA256; presskey = malloc_cache_aligned(MAX_DELAY_STOP_STR); c = strstr(sha_env_str, ":"); @@ -245,10 +304,14 @@ static int abortboot_key_sequence(int bootdelay) printf(CONFIG_AUTOBOOT_PROMPT, bootdelay); # endif - if (IS_ENABLED(CONFIG_AUTOBOOT_ENCRYPTION)) - abort = passwd_abort_sha256(etime); - else + if (IS_ENABLED(CONFIG_AUTOBOOT_ENCRYPTION)) { + if (IS_ENABLED(CONFIG_CRYPT_PW)) + abort = passwd_abort_crypt(etime); + else + abort = passwd_abort_sha256(etime); + } else { abort = passwd_abort_key(etime); + } if (!abort) debug_bootkeys("key timeout\n"); @@ -394,4 +457,4 @@ void autoboot_command(const char *s) if (s) run_command_list(s, -1, 0); } -} +} \ No newline at end of file From 1b2d68033be5703bef482498d81e07c19f69084e Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:36 +0200 Subject: [PATCH 07/11] common: Rename macro appropriately While doing code-review internally this got nitpicked by 2 reviewers, so I decided to include this here. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass Reviewed-by: Heiko Schocher --- common/autoboot.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/autoboot.c b/common/autoboot.c index 107fb1533d..832ef7c78c 100644 --- a/common/autoboot.c +++ b/common/autoboot.c @@ -27,7 +27,7 @@ DECLARE_GLOBAL_DATA_PTR; -#define MAX_DELAY_STOP_STR 64 +#define DELAY_STOP_STR_MAX_LENGTH 64 #ifndef DEBUG_BOOTKEYS #define DEBUG_BOOTKEYS 0 @@ -69,7 +69,7 @@ static int menukey; static int passwd_abort_crypt(uint64_t etime) { const char *crypt_env_str = env_get("bootstopkeycrypt"); - char presskey[MAX_DELAY_STOP_STR]; + char presskey[DELAY_STOP_STR_MAX_LENGTH]; u_int presskey_len = 0; int abort = 0; int err; @@ -150,9 +150,9 @@ static int passwd_abort_sha256(uint64_t etime) if (sha_env_str == NULL) sha_env_str = CONFIG_AUTOBOOT_STOP_STR_SHA256; - presskey = malloc_cache_aligned(MAX_DELAY_STOP_STR); + presskey = malloc_cache_aligned(DELAY_STOP_STR_MAX_LENGTH); c = strstr(sha_env_str, ":"); - if (c && (c - sha_env_str < MAX_DELAY_STOP_STR)) { + if (c && (c - sha_env_str < DELAY_STOP_STR_MAX_LENGTH)) { /* preload presskey with salt */ memcpy(presskey, sha_env_str, c - sha_env_str); presskey_len = c - sha_env_str; @@ -179,7 +179,7 @@ static int passwd_abort_sha256(uint64_t etime) do { if (tstc()) { /* Check for input string overflow */ - if (presskey_len >= MAX_DELAY_STOP_STR) { + if (presskey_len >= DELAY_STOP_STR_MAX_LENGTH) { free(presskey); free(sha); return 0; @@ -223,7 +223,7 @@ static int passwd_abort_key(uint64_t etime) { .str = env_get("bootstopkey"), .retry = 0 }, }; - char presskey[MAX_DELAY_STOP_STR]; + char presskey[DELAY_STOP_STR_MAX_LENGTH]; int presskey_len = 0; int presskey_max = 0; int i; @@ -240,8 +240,8 @@ static int passwd_abort_key(uint64_t etime) for (i = 0; i < sizeof(delaykey) / sizeof(delaykey[0]); i++) { delaykey[i].len = delaykey[i].str == NULL ? 0 : strlen(delaykey[i].str); - delaykey[i].len = delaykey[i].len > MAX_DELAY_STOP_STR ? - MAX_DELAY_STOP_STR : delaykey[i].len; + delaykey[i].len = delaykey[i].len > DELAY_STOP_STR_MAX_LENGTH ? + DELAY_STOP_STR_MAX_LENGTH : delaykey[i].len; presskey_max = presskey_max > delaykey[i].len ? presskey_max : delaykey[i].len; From 6c0ce6d3ca273f12e1cb4018144bee8e6131d30d Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:37 +0200 Subject: [PATCH 08/11] common: allow disabling of timeout for password entry In case a user has to enter a complicated password it is sometimes desireable to give the user more time than the default timeout. Enabling this feature will disable the timeout entirely in case the user presses the key before entering any other character. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass --- common/Kconfig.boot | 8 ++++++++ common/autoboot.c | 12 +++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/common/Kconfig.boot b/common/Kconfig.boot index fe60ad0171..f9551b206f 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -860,6 +860,14 @@ config AUTOBOOT_KEYED_CTRLC Setting this variable provides an escape sequence from the limited "password" strings. +config AUTOBOOT_NEVER_TIMEOUT + bool "Make the password entry never time-out" + depends on AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION && CRYPT_PW + help + This option removes the timeout from the password entry + when the user first presses the key before entering + any other character. + config AUTOBOOT_STOP_STR_ENABLE bool "Enable fixed string to stop autobooting" depends on AUTOBOOT_KEYED && AUTOBOOT_ENCRYPTION diff --git a/common/autoboot.c b/common/autoboot.c index 832ef7c78c..2564ef8a56 100644 --- a/common/autoboot.c +++ b/common/autoboot.c @@ -63,6 +63,10 @@ static int menukey; * or * the config value CONFIG_AUTOBOOT_STOP_STR_CRYPT * + * In case the config value CONFIG_AUTOBOOT_NEVER_TIMEOUT has been enabled + * this function never times out if the user presses the key + * before starting to enter the password. + * * @etime: Timeout value ticks (stop when get_ticks() reachs this) * @return 0 if autoboot should continue, 1 if it should stop */ @@ -72,6 +76,7 @@ static int passwd_abort_crypt(uint64_t etime) char presskey[DELAY_STOP_STR_MAX_LENGTH]; u_int presskey_len = 0; int abort = 0; + int never_timeout = 0; int err; if (IS_ENABLED(CONFIG_AUTOBOOT_STOP_STR_ENABLE) && !crypt_env_str) @@ -91,6 +96,11 @@ static int passwd_abort_crypt(uint64_t etime) if ((presskey[presskey_len] == '\r') || (presskey[presskey_len] == '\n')) { + if (IS_ENABLED(CONFIG_AUTOBOOT_NEVER_TIMEOUT) && + !presskey_len) { + never_timeout = 1; + continue; + } presskey[presskey_len] = '\0'; err = crypt_compare(crypt_env_str, presskey, &abort); @@ -104,7 +114,7 @@ static int passwd_abort_crypt(uint64_t etime) presskey_len++; } } - } while (get_ticks() <= etime); + } while (never_timeout || get_ticks() <= etime); return abort; } From d199c3ab1c3afa7a17259f4045516f5fbfaaa446 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:38 +0200 Subject: [PATCH 09/11] common: add AUTOBOOT_FLUSH_STDIN option The key-sequence based unlock mechanisms are sensitive to junk symbols that could have been sent to stdin and are still waiting to be retrieved. Enabling this option will read all symbols off stdin before displaying the autoboot prompt (and starting to read the password from stdin). Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass --- common/Kconfig.boot | 9 +++++++++ common/autoboot.c | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/common/Kconfig.boot b/common/Kconfig.boot index f9551b206f..e0cca226da 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -791,6 +791,15 @@ config AUTOBOOT_KEYED U-Boot automatic booting process and bring the device to the U-Boot prompt for user input. +config AUTOBOOT_FLUSH_STDIN + bool "Enable flushing stdin before starting to read the password" + depends on AUTOBOOT_KEYED && !SANDBOX + help + When this option is enabled stdin buffer will be flushed before + starting to read the password. + This can't be enabled for the sandbox as flushing stdin would + break the autoboot unit tests. + config AUTOBOOT_PROMPT string "Autoboot stop prompt" depends on AUTOBOOT_KEYED diff --git a/common/autoboot.c b/common/autoboot.c index 2564ef8a56..35ef526c42 100644 --- a/common/autoboot.c +++ b/common/autoboot.c @@ -297,6 +297,15 @@ static int passwd_abort_key(uint64_t etime) return abort; } +/** + * flush_stdin() - drops all pending characters from stdin + */ +static void flush_stdin(void) +{ + while (tstc()) + (void)getchar(); +} + /*************************************************************************** * Watch for 'delay' seconds for autoboot stop or autoboot delay string. * returns: 0 - no key string, allow autoboot 1 - got key string, abort @@ -306,6 +315,8 @@ static int abortboot_key_sequence(int bootdelay) int abort; uint64_t etime = endtick(bootdelay); + if (IS_ENABLED(CONFIG_AUTOBOOT_FLUSH_STDIN)) + flush_stdin(); # ifdef CONFIG_AUTOBOOT_PROMPT /* * CONFIG_AUTOBOOT_PROMPT includes the %d for all boards. From 33198740aca2d68e9760cfd6ebb5a55894431966 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:39 +0200 Subject: [PATCH 10/11] common: add support to fallback to plain SHA256 In case crypt-based hashing is enabled this will be the default mechanism that is used. If a user wants to have support for both, the environment variable `bootstopusesha256` can be set to `true` to allow plain SHA256 based hashing of the password. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass --- common/Kconfig.boot | 8 ++++++++ common/autoboot.c | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/common/Kconfig.boot b/common/Kconfig.boot index e0cca226da..49e28b2ef2 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -835,6 +835,14 @@ config AUTOBOOT_ENCRYPTION This provides a way to ship a secure production device which can also be accessed at the U-Boot command line. +config AUTOBOOT_SHA256_FALLBACK + bool "Allow fallback from crypt-hashed password to sha256" + depends on AUTOBOOT_ENCRYPTION && CRYPT_PW + help + This option adds support to fall back from crypt-hashed + passwords to checking a SHA256 hashed password in case the + 'bootstopusesha256' environment variable is set to 'true'. + config AUTOBOOT_DELAY_STR string "Delay autobooting via specific input key / string" depends on AUTOBOOT_KEYED && !AUTOBOOT_ENCRYPTION diff --git a/common/autoboot.c b/common/autoboot.c index 35ef526c42..8b9e9aa878 100644 --- a/common/autoboot.c +++ b/common/autoboot.c @@ -306,6 +306,26 @@ static void flush_stdin(void) (void)getchar(); } +/** + * fallback_to_sha256() - check whether we should fall back to sha256 + * password checking + * + * This checks for the environment variable `bootstopusesha256` in case + * sha256-fallback has been enabled via the config setting + * `AUTOBOOT_SHA256_FALLBACK`. + * + * @return `false` if we must not fall-back, `true` if plain sha256 should be tried + */ +static bool fallback_to_sha256(void) +{ + if (IS_ENABLED(CONFIG_AUTOBOOT_SHA256_FALLBACK)) + return env_get_yesno("bootstopusesha256") == 1; + else if (IS_ENABLED(CONFIG_CRYPT_PW)) + return false; + else + return true; +} + /*************************************************************************** * Watch for 'delay' seconds for autoboot stop or autoboot delay string. * returns: 0 - no key string, allow autoboot 1 - got key string, abort @@ -326,7 +346,7 @@ static int abortboot_key_sequence(int bootdelay) # endif if (IS_ENABLED(CONFIG_AUTOBOOT_ENCRYPTION)) { - if (IS_ENABLED(CONFIG_CRYPT_PW)) + if (IS_ENABLED(CONFIG_CRYPT_PW) && !fallback_to_sha256()) abort = passwd_abort_crypt(etime); else abort = passwd_abort_sha256(etime); From 25c8b9f298e46ea6048b5308f7ee207c6461c36a Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:40 +0200 Subject: [PATCH 11/11] test: add first autoboot unit tests This adds tests for the crypt-based and plain SHA256-based password hashing algorithms in the autoboot flow. Signed-off-by: Steffen Jaeckel Reviewed-by: Simon Glass --- common/Kconfig.boot | 2 +- common/console.c | 5 +++ configs/sandbox_defconfig | 10 +++++ include/console.h | 17 +++++++ include/test/common.h | 15 +++++++ include/test/suites.h | 1 + test/Makefile | 1 + test/cmd_ut.c | 1 + test/common/Makefile | 3 ++ test/common/cmd_ut_common.c | 22 +++++++++ test/common/test_autoboot.c | 90 +++++++++++++++++++++++++++++++++++++ 11 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 include/test/common.h create mode 100644 test/common/Makefile create mode 100644 test/common/cmd_ut_common.c create mode 100644 test/common/test_autoboot.c diff --git a/common/Kconfig.boot b/common/Kconfig.boot index 49e28b2ef2..642dd9bcfb 100644 --- a/common/Kconfig.boot +++ b/common/Kconfig.boot @@ -907,7 +907,7 @@ config AUTOBOOT_STOP_STR_CRYPT and saved in the environment variable "bootstopkeycrypt". config AUTOBOOT_STOP_STR_SHA256 - string "Stop autobooting via SHA256 encrypted password" + string "Stop autobooting via SHA256 hashed password" depends on AUTOBOOT_STOP_STR_ENABLE help This option adds the feature to only stop the autobooting, diff --git a/common/console.c b/common/console.c index 73edb28799..0013d183ae 100644 --- a/common/console.c +++ b/common/console.c @@ -773,6 +773,11 @@ int console_record_avail(void) return membuff_avail((struct membuff *)&gd->console_out); } +int console_in_puts(const char *str) +{ + return membuff_put((struct membuff *)&gd->console_in, str, strlen(str)); +} + #endif /* test if ctrl-c was pressed */ diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 1766dc018b..a1e77a511d 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -287,6 +287,16 @@ CONFIG_EFI_CAPSULE_FIRMWARE_FIT=y CONFIG_EFI_CAPSULE_FIRMWARE_RAW=y CONFIG_EFI_SECURE_BOOT=y CONFIG_TEST_FDTDEC=y +CONFIG_CRYPT_PW=y +CONFIG_CRYPT_PW_SHA256=y +CONFIG_CRYPT_PW_SHA512=y +CONFIG_AUTOBOOT_KEYED=y +CONFIG_AUTOBOOT_PROMPT="Enter password \"a\" in %d seconds to stop autoboot\n" +CONFIG_AUTOBOOT_ENCRYPTION=y +CONFIG_AUTOBOOT_STOP_STR_ENABLE=y +CONFIG_AUTOBOOT_STOP_STR_CRYPT="$5$rounds=640000$HrpE65IkB8CM5nCL$BKT3QdF98Bo8fJpTr9tjZLZQyzqPASBY20xuK5Rent9" +CONFIG_AUTOBOOT_NEVER_TIMEOUT=y +CONFIG_AUTOBOOT_SHA256_FALLBACK=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y CONFIG_UT_DM=y diff --git a/include/console.h b/include/console.h index f848bcbf03..b182440fcd 100644 --- a/include/console.h +++ b/include/console.h @@ -83,6 +83,17 @@ int console_record_readline(char *str, int maxlen); * @return available bytes (0 if empty) */ int console_record_avail(void); + +/** + * console_in_puts() - Write a string to the console input buffer + * + * This writes the given string to the console_in buffer which will then be + * returned if a function calls e.g. `getc()` + * + * @str: the string to write + * @return the number of bytes added + */ +int console_in_puts(const char *str); #else static inline int console_record_init(void) { @@ -114,6 +125,12 @@ static inline int console_record_avail(void) return 0; } +static inline int console_in_puts(const char *str) +{ + /* There is never anything written */ + return 0; +} + #endif /* !CONFIG_CONSOLE_RECORD */ /** diff --git a/include/test/common.h b/include/test/common.h new file mode 100644 index 0000000000..81260d06ad --- /dev/null +++ b/include/test/common.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019 Heinrich Schuchardt + * Copyright (c) 2021 Steffen Jaeckel + */ + +#ifndef __TEST_COMMON_H__ +#define __TEST_COMMON_H__ + +#include + +/* Declare a new common function test */ +#define COMMON_TEST(_name, _flags) UNIT_TEST(_name, _flags, common_test) + +#endif /* __TEST_COMMON_H__ */ diff --git a/include/test/suites.h b/include/test/suites.h index 80b41f188c..d35cd83a4e 100644 --- a/include/test/suites.h +++ b/include/test/suites.h @@ -31,6 +31,7 @@ int do_ut_addrmap(struct cmd_tbl *cmdtp, int flag, int argc, int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); +int do_ut_common(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_compression(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); int do_ut_dm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]); diff --git a/test/Makefile b/test/Makefile index 117839e584..b3b2902e2e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_UT_TIME) += time_ut.o obj-y += ut.o ifeq ($(CONFIG_SPL_BUILD),) +obj-$(CONFIG_UNIT_TEST) += common/ obj-$(CONFIG_UNIT_TEST) += lib/ obj-y += log/ obj-$(CONFIG_$(SPL_)UT_UNICODE) += unicode_ut.o diff --git a/test/cmd_ut.c b/test/cmd_ut.c index 6f174c6a07..90b260f72d 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -28,6 +28,7 @@ int cmd_ut_category(const char *name, const char *prefix, static struct cmd_tbl cmd_ut_sub[] = { U_BOOT_CMD_MKENT(all, CONFIG_SYS_MAXARGS, 1, do_ut_all, "", ""), + U_BOOT_CMD_MKENT(common, CONFIG_SYS_MAXARGS, 1, do_ut_common, "", ""), #if defined(CONFIG_UT_DM) U_BOOT_CMD_MKENT(dm, CONFIG_SYS_MAXARGS, 1, do_ut_dm, "", ""), #endif diff --git a/test/common/Makefile b/test/common/Makefile new file mode 100644 index 0000000000..24c9145dcc --- /dev/null +++ b/test/common/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ +obj-y += cmd_ut_common.o +obj-$(CONFIG_AUTOBOOT) += test_autoboot.o diff --git a/test/common/cmd_ut_common.c b/test/common/cmd_ut_common.c new file mode 100644 index 0000000000..2c0267801b --- /dev/null +++ b/test/common/cmd_ut_common.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2019 Heinrich Schuchardt + * Copyright (c) 2021 Steffen Jaeckel + * + * Unit tests for common functions + */ + +#include +#include +#include +#include +#include + +int do_ut_common(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) +{ + struct unit_test *tests = UNIT_TEST_SUITE_START(common_test); + const int n_ents = UNIT_TEST_SUITE_COUNT(common_test); + + return cmd_ut_category("common", "common_test_", tests, n_ents, argc, + argv); +} diff --git a/test/common/test_autoboot.c b/test/common/test_autoboot.c new file mode 100644 index 0000000000..6564ac7049 --- /dev/null +++ b/test/common/test_autoboot.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2021 Steffen Jaeckel + * + * Unit tests for autoboot functionality + */ + +#include +#include +#include +#include +#include + +#include + +static int check_for_input(struct unit_test_state *uts, const char *in, + bool correct) +{ + /* The bootdelay is set to 1 second in test_autoboot() */ + const char *autoboot_prompt = + "Enter password \"a\" in 1 seconds to stop autoboot"; + + console_record_reset_enable(); + console_in_puts(in); + autoboot_command("echo Autoboot password unlock not successful"); + ut_assert_nextline(autoboot_prompt); + if (!correct) + ut_assert_nextline("Autoboot password unlock not successful"); + ut_assert_console_end(); + return 0; +} + +/** + * test_autoboot() - unit test for autoboot + * + * @uts: unit test state + * Return: 0 = success, 1 = failure + */ +static int test_autoboot(struct unit_test_state *uts) +{ + /* make sure that the bootdelay is set to something, + * otherwise the called functions will time out + */ + ut_assertok(env_set("bootdelay", "1")); + bootdelay_process(); + + /* unset all relevant environment variables */ + env_set("bootstopusesha256", NULL); + env_set("bootstopkeycrypt", NULL); + env_set("bootstopkeysha256", NULL); + + if (IS_ENABLED(CONFIG_CRYPT_PW_SHA256)) { + /* test the default password from CONFIG_AUTOBOOT_STOP_STR_CRYPT */ + ut_assertok(check_for_input(uts, "a\n", true)); + /* test a password from the `bootstopkeycrypt` environment variable */ + ut_assertok(env_set( + "bootstopkeycrypt", + "$5$rounds=640000$ycgRgpnRq4lmu.eb$aZ6YJWdklvyLML13w7mEHMHJnJOux6aptnp6VlsR5a9")); + + ut_assertok(check_for_input(uts, "test\n", true)); + + /* verify that the `bootstopusesha256` variable is treated correctly */ + ut_assertok(env_set("bootstopusesha256", "false")); + ut_assertok(check_for_input(uts, "test\n", true)); + } + + if (IS_ENABLED(CONFIG_AUTOBOOT_ENCRYPTION)) { + /* test the `bootstopusesha256` and `bootstopkeysha256` features */ + ut_assertok(env_set("bootstopusesha256", "true")); + ut_assertok(env_set( + "bootstopkeysha256", + "edeaaff3f1774ad2888673770c6d64097e391bc362d7d6fb34982ddf0efd18cb")); + + ut_assertok(check_for_input(uts, "abc\n", true)); + + ut_assertok(env_set( + "bootstopkeysha256", + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")); + + ut_assertok(check_for_input(uts, "abc", true)); + + ut_assertok(check_for_input(uts, "abc\n", true)); + + ut_assertok(check_for_input(uts, "abd", false)); + } + + return CMD_RET_SUCCESS; +} + +COMMON_TEST(test_autoboot, 0);