提交 edecc15e 编写于 作者: T Tom Rini

Merge branch '2021-07-23-reboot-mode-and-cryptfs-passwd-support'

- A new driver uclass is created to handle the reboot mode control.
- Add support for libcrypt-style passwords for autoboot
......@@ -59,6 +59,24 @@
};
};
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>;
};
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>;
......
......@@ -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
......@@ -812,13 +821,28 @@ 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.
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
......@@ -853,9 +877,38 @@ config AUTOBOOT_KEYED_CTRLC
Setting this variable provides an escape sequence from the
limited "password" strings.
config AUTOBOOT_STOP_STR_SHA256
string "Stop autobooting via SHA256 encrypted password"
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 <Enter> key before entering
any other character.
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 hashed password"
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
......
......@@ -23,10 +23,11 @@
#include <linux/delay.h>
#include <u-boot/sha256.h>
#include <bootcount.h>
#include <crypt.h>
DECLARE_GLOBAL_DATA_PTR;
#define MAX_DELAY_STOP_STR 64
#define DELAY_STOP_STR_MAX_LENGTH 64
#ifndef DEBUG_BOOTKEYS
#define DEBUG_BOOTKEYS 0
......@@ -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,73 @@ 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
*
* In case the config value CONFIG_AUTOBOOT_NEVER_TIMEOUT has been enabled
* this function never times out if the user presses the <Enter> 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
*/
static int passwd_abort_crypt(uint64_t etime)
{
const char *crypt_env_str = env_get("bootstopkeycrypt");
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)
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')) {
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);
if (err)
debug_bootkeys(
"crypt_compare() failed with: %s\n",
errno_str(err));
/* you had one chance */
break;
} else {
presskey_len++;
}
}
} while (never_timeout || get_ticks() <= etime);
return abort;
}
/*
* Use a "constant-length" time compare function for this
* hash compare:
......@@ -89,11 +158,11 @@ 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);
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;
......@@ -120,7 +189,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;
......@@ -164,7 +233,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;
......@@ -181,8 +250,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;
......@@ -228,6 +297,35 @@ 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();
}
/**
* 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
......@@ -237,6 +335,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.
......@@ -245,10 +345,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) && !fallback_to_sha256())
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 +498,4 @@ void autoboot_command(const char *s)
if (s)
run_command_list(s, -1, 0);
}
}
}
\ No newline at end of file
......@@ -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 */
......
......@@ -287,6 +287,19 @@ 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
CONFIG_DM_REBOOT_MODE=y
CONFIG_DM_REBOOT_MODE_GPIO=y
CONFIG_DM_REBOOT_MODE_RTC=y
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>;
};
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>;
};
......@@ -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"
......
......@@ -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/
......
#
# 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.
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.
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
#
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (c), Vaisala Oyj
#
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
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c), Vaisala Oyj
*/
#include <common.h>
#include <asm/gpio.h>
#include <dm.h>
#include <dm/devres.h>
#include <errno.h>
#include <reboot-mode/reboot-mode-gpio.h>
#include <reboot-mode/reboot-mode.h>
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,
};
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c), Vaisala Oyj
*/
#include <common.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <reboot-mode/reboot-mode-rtc.h>
#include <reboot-mode/reboot-mode.h>
#include <rtc.h>
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,
};
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c), Vaisala Oyj
*/
#include <common.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <exports.h>
#include <reboot-mode/reboot-mode.h>
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),
};
......@@ -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 */
/**
......
/* SPDX-License-Identifier: GPL-2.0+ */
/* Copyright (C) 2020 Steffen Jaeckel <jaeckel-floss@eyet-services.de> */
/**
* 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
* @return 0 on success, error code of errno else
*/
int crypt_compare(const char *should, const char *passphrase, int *equal);
......@@ -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 */
......
/* 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 <asm/gpio.h>
/*
* 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_ */
/* 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_ */
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (c), Vaisala Oyj
*/
#ifndef REBOOT_MODE_REBOOT_MODE_H__
#define REBOOT_MODE_REBOOT_MODE_H__
#include <asm/types.h>
#include <dm/device.h>
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__ */
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2019 Heinrich Schuchardt <xypron.glpk@gmx.de>
* Copyright (c) 2021 Steffen Jaeckel <jaeckel-floss@eyet-services.de>
*/
#ifndef __TEST_COMMON_H__
#define __TEST_COMMON_H__
#include <test/test.h>
/* Declare a new common function test */
#define COMMON_TEST(_name, _flags) UNIT_TEST(_name, _flags, common_test)
#endif /* __TEST_COMMON_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[]);
......
......@@ -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"
......
......@@ -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/
......
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
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (c) 2021, Steffen Jaeckel <jaeckel-floss@eyet-services.de>
obj-$(CONFIG_CRYPT_PW) += crypt.o
obj-$(CONFIG_CRYPT_PW_SHA256) += crypt-sha256.o
obj-$(CONFIG_CRYPT_PW_SHA512) += crypt-sha512.o
/* SPDX-License-Identifier: GPL-2.0+ */
/* Copyright (C) 2020 Steffen Jaeckel <jaeckel-floss@eyet-services.de> */
#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)
/* SPDX-License-Identifier: GPL-2.0+ */
/* Copyright (C) 2020 Steffen Jaeckel <jaeckel-floss@eyet-services.de> */
#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)
/* SPDX-License-Identifier: GPL-2.0+ */
/* Copyright (C) 2020 Steffen Jaeckel <jaeckel-floss@eyet-services.de> */
#include <linux/types.h>
#include <vsprintf.h>
#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)
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);
// 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 <drepper at redhat.com> in 2007 [1].
* Modified by Zack Weinberg <zackw at panix.com> in 2017, 2018.
* Composed by Björn Esser <besser82 at fedoraproject.org> in 2018.
* Modified by Björn Esser <besser82 at fedoraproject.org> in 2020.
* Modified by Steffen Jaeckel <jaeckel-floss at eyet-services.de> 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.
*
* 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 <linux/errno.h>
#include <stdio.h>
#include <stdlib.h>
#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");
/* 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
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
// 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 <drepper at redhat.com> in 2007 [1].
* Modified by Zack Weinberg <zackw at panix.com> in 2017, 2018.
* Composed by Björn Esser <besser82 at fedoraproject.org> in 2018.
* Modified by Björn Esser <besser82 at fedoraproject.org> in 2020.
* Modified by Steffen Jaeckel <jaeckel-floss at eyet-services.de> 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.
*
* 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 <linux/errno.h>
#include <stdio.h>
#include <stdlib.h>
#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");
/* 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. */
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
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (C) 2020 Steffen Jaeckel <jaeckel-floss@eyet-services.de> */
#include <common.h>
#include <crypt.h>
#include "crypt-port.h"
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";
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;
}
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_wrapped },
#endif
#if defined(CONFIG_CRYPT_PW_SHA512)
{ "$6$", crypt_sha512crypt_rn_wrapped },
#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 -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 (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;
}
......@@ -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
......
......@@ -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
......
......@@ -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
......
# SPDX-License-Identifier: GPL-2.0+
obj-y += cmd_ut_common.o
obj-$(CONFIG_AUTOBOOT) += test_autoboot.o
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 Heinrich Schuchardt <xypron.glpk@gmx.de>
* Copyright (c) 2021 Steffen Jaeckel <jaeckel-floss@eyet-services.de>
*
* Unit tests for common functions
*/
#include <common.h>
#include <command.h>
#include <test/common.h>
#include <test/suites.h>
#include <test/ut.h>
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);
}
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2021 Steffen Jaeckel
*
* Unit tests for autoboot functionality
*/
#include <autoboot.h>
#include <common.h>
#include <test/common.h>
#include <test/test.h>
#include <test/ut.h>
#include <crypt.h>
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);
......@@ -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
......
// SPDX-License-Identifier: GPL-2.0+
/*
* (C) 2018 Theobroma Systems Design und Consulting GmbH
*/
#include <common.h>
#include <dm.h>
#include <reboot-mode/reboot-mode.h>
#include <env.h>
#include <log.h>
#include <asm/gpio.h>
#include <asm/rtc.h>
#include <asm/test.h>
#include <dm/test.h>
#include <test/test.h>
#include <test/ut.h>
#include <rtc.h>
#include <linux/byteorder/generic.h>
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);
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);
......@@ -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
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2021 Steffen Jaeckel
*
* Unit test for crypt-style password hashing
*/
#include <common.h>
#include <test/lib.h>
#include <test/test.h>
#include <test/ut.h>
#include <crypt.h>
/**
* 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;
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)) {
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)) {
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");
}
return CMD_RET_SUCCESS;
}
LIB_TEST(lib_crypt, 0);
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册