From 2541ce2c1af87f74a9feb35a1cbfc20ff8d04e4b Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:43 +0300 Subject: 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 --- include/dm/uclass-id.h | 1 + include/reboot-mode/reboot-mode.h | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 include/reboot-mode/reboot-mode.h (limited to 'include') diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index d800f679d56..9d474533ba8 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 00000000000..86b51f881ca --- /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__ */ -- cgit v1.3.1 From f9db2f16cb6fc7b6d05b0e70de65881bc97ba5c2 Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:44 +0300 Subject: 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 (limited to 'include') diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 0cee15a0ea2..2347f4cbe4f 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 1655bb1e8a2..6931b176ed9 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 00000000000..bb209d2742d --- /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 0edc3209d71..ff65e2031ae 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 2ab0fddac9e..04917be4f43 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 00000000000..305174736ed --- /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 00000000000..16b1185c698 --- /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 9ef9171a1cb..d5c42e7643e 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 00000000000..66aa4793f7b --- /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); -- cgit v1.3.1 From c74675bd904b6ce9d5820a80f27793c0583fd54c Mon Sep 17 00:00:00 2001 From: Nandor Han Date: Thu, 10 Jun 2021 16:56:45 +0300 Subject: 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 (limited to 'include') diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 2347f4cbe4f..d5976318d1c 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 6931b176ed9..1766dc018b6 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 00000000000..23aa12c88c2 --- /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 ff65e2031ae..ac67bfcef62 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 04917be4f43..2c13780ced4 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 00000000000..972d0cdbcb5 --- /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 00000000000..3613678f636 --- /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 66aa4793f7b..fbb9c3a5426 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); -- cgit v1.3.1 From 26dd9936574864155b989b9f14319ca2779f0598 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:33 +0200 Subject: 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 (limited to 'include') diff --git a/include/crypt.h b/include/crypt.h new file mode 100644 index 00000000000..e0be2832ff2 --- /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 ad0cd52edd8..ad4d75e0a40 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 d8055d36d6e..5cd0c9c6389 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 00000000000..5495ae8d4cd --- /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 00000000000..d53b72b0c7a --- /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 00000000000..e4b29c9f317 --- /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 00000000000..93b6109faef --- /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 00000000000..680ffe93499 --- /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 00000000000..37127d41e14 --- /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 00000000000..3616019445f --- /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 00000000000..4ec6079768b --- /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 ab3ac54a1b2..e15ba239eb3 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 aa2e66bc7f4..6fd05142510 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 00000000000..277e4efed16 --- /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); -- cgit v1.3.1 From 29bbe71ccfef3440b4881259c6f8e39b6e7924c6 Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:34 +0200 Subject: 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(-) (limited to 'include') diff --git a/include/crypt.h b/include/crypt.h index e0be2832ff2..f18a1705d4d 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 e4b29c9f317..62e7b9d5c09 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 93b6109faef..47e45730cc0 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 680ffe93499..6b9542d75bc 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 37127d41e14..335c8880d81 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 3616019445f..8c8e6dd3dea 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 4ec6079768b..247c34b2a9c 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 277e4efed16..fb21edf9748 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"); } -- cgit v1.3.1 From 25c8b9f298e46ea6048b5308f7ee207c6461c36a Mon Sep 17 00:00:00 2001 From: Steffen Jaeckel Date: Thu, 8 Jul 2021 15:57:40 +0200 Subject: 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 (limited to 'include') diff --git a/common/Kconfig.boot b/common/Kconfig.boot index 49e28b2ef29..642dd9bcfbe 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 73edb287992..0013d183aeb 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 1766dc018b6..a1e77a511db 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 f848bcbf037..b182440fcd6 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 00000000000..81260d06ad6 --- /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 80b41f188c7..d35cd83a4eb 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 117839e5847..b3b2902e2e7 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 6f174c6a07f..90b260f72d6 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 00000000000..24c9145dccc --- /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 00000000000..2c0267801b2 --- /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 00000000000..6564ac70496 --- /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); -- cgit v1.3.1