From: Simon Glass <simon.glass@canonical.com> Modern systems mostly use LUKSv2 as it is more secure that v1. This series provides an implementation of this feature, making use of the existing 'luks unlock' command. One interesting part of this series is a converter from JSON to FDT, so that U-Boot's existing ofnode interface can be used to access the hierarchical data in JSON text. This obviously results in quite a bit of new code, but it is more robust than trying to parse the text directly using strstr(), etc. The choice of JSON for LUKS was presumably made with larger code bases in mind, rather than a firmware implementation. Simon Glass (15): mbedtls: Allow use of XTS functions mbedtls: Allow use of base64 test: Check for null string in assert functions json: Provide a way to convert JSON to FDT lib: Bring in argon2 library lib: Adapt argon2 library for U-Boot lib: Plumb in argon2 library test: Shorten the encrypt_passphrase parameter for FsHelper test: Add a way to create a LUKS2 partition with XTS test: Switch mmc12 over to use argon2id luks: Export the af_merge() function luks: Tidy up the code style in the block driver luks: Provide an implementation of luks2 luks: Enable LUKSv2 support in the luks command luks: Update docs and tests for LUKSv2 cmd/luks.c | 5 +- configs/sandbox_defconfig | 4 +- doc/usage/cmd/luks.rst | 42 +- doc/usage/luks.rst | 302 +++++++-- drivers/block/Makefile | 2 +- drivers/block/luks.c | 233 +++++-- drivers/block/luks2.c | 974 +++++++++++++++++++++++++++ drivers/block/luks_internal.h | 43 ++ drivers/misc/Kconfig | 2 +- fs/btrfs/Kconfig | 2 +- include/argon2.h | 448 ++++++++++++ include/json.h | 34 + include/luks.h | 4 +- include/test/ut.h | 28 +- lib/Kconfig | 14 +- lib/Makefile | 4 + lib/argon2/Makefile | 10 + lib/argon2/argon2_wrapper.c | 469 +++++++++++++ lib/argon2/blake2/blake2-impl.h | 156 +++++ lib/argon2/blake2/blake2.h | 90 +++ lib/argon2/blake2/blake2b.c | 391 +++++++++++ lib/argon2/blake2/blamka-round-ref.h | 57 ++ lib/argon2/core.c | 616 +++++++++++++++++ lib/argon2/core.h | 229 +++++++ lib/argon2/ref.c | 195 ++++++ lib/json.c | 612 ++++++++++++++++- lib/mbedtls/Makefile | 5 + lib/mbedtls/mbedtls_def_config.h | 8 + test/boot/luks.c | 29 + test/lib/json.c | 337 ++++++++- test/py/img/common.py | 9 +- test/py/img/ubuntu.py | 6 +- test/py/tests/fs_helper.py | 52 +- test/py/tests/test_ut.py | 3 +- 34 files changed, 5256 insertions(+), 159 deletions(-) create mode 100644 drivers/block/luks2.c create mode 100644 drivers/block/luks_internal.h create mode 100644 include/argon2.h create mode 100644 lib/argon2/Makefile create mode 100644 lib/argon2/argon2_wrapper.c create mode 100644 lib/argon2/blake2/blake2-impl.h create mode 100644 lib/argon2/blake2/blake2.h create mode 100644 lib/argon2/blake2/blake2b.c create mode 100644 lib/argon2/blake2/blamka-round-ref.h create mode 100644 lib/argon2/core.c create mode 100644 lib/argon2/core.h create mode 100644 lib/argon2/ref.c -- 2.43.0 base-commit: 39a81c884d1750071549da405fc5fdfe1f1270c9 branch: sece
From: Simon Glass <simon.glass@canonical.com> Add a few Kconfig options to support XTS (XEX Tweakable Block Ciphertext Stealing). Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- lib/mbedtls/Makefile | 4 ++++ lib/mbedtls/mbedtls_def_config.h | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/lib/mbedtls/Makefile b/lib/mbedtls/Makefile index 0506a5a6b3e..257f585c013 100644 --- a/lib/mbedtls/Makefile +++ b/lib/mbedtls/Makefile @@ -37,6 +37,10 @@ mbedtls_lib_crypto-$(CONFIG_$(PHASE_)HKDF_MBEDTLS) += \ $(MBEDTLS_LIB_DIR)/hkdf.o mbedtls_lib_crypto-$(CONFIG_$(PHASE_)PKCS5_MBEDTLS) += \ $(MBEDTLS_LIB_DIR)/pkcs5.o +mbedtls_lib_crypto-$(CONFIG_$(PHASE_)BLK_LUKS) += \ + $(MBEDTLS_LIB_DIR)/aes.o \ + $(MBEDTLS_LIB_DIR)/cipher.o \ + $(MBEDTLS_LIB_DIR)/cipher_wrap.o # MbedTLS X509 library obj-$(CONFIG_$(XPL_)MBEDTLS_LIB_X509) += mbedtls_lib_x509.o diff --git a/lib/mbedtls/mbedtls_def_config.h b/lib/mbedtls/mbedtls_def_config.h index 9e3beed07f4..a0578d33ba6 100644 --- a/lib/mbedtls/mbedtls_def_config.h +++ b/lib/mbedtls/mbedtls_def_config.h @@ -64,6 +64,12 @@ #define MBEDTLS_PKCS5_C #endif +#if CONFIG_IS_ENABLED(BLK_LUKS) +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CIPHER_MODE_XTS +#define MBEDTLS_AES_C +#endif + #if CONFIG_IS_ENABLED(MBEDTLS_LIB_X509) #if CONFIG_IS_ENABLED(X509_CERTIFICATE_PARSER) @@ -104,6 +110,7 @@ #define MBEDTLS_SSL_CLI_C #define MBEDTLS_SSL_TLS_C #define MBEDTLS_CIPHER_C +#define MBEDTLS_CIPHER_MODE_XTS #define MBEDTLS_MD_C #define MBEDTLS_CTR_DRBG_C #define MBEDTLS_AES_C -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a configuration and a Makefile rule to provide access to the mbedtls base64 support. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- lib/mbedtls/Makefile | 1 + lib/mbedtls/mbedtls_def_config.h | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/mbedtls/Makefile b/lib/mbedtls/Makefile index 257f585c013..5b3f664073c 100644 --- a/lib/mbedtls/Makefile +++ b/lib/mbedtls/Makefile @@ -39,6 +39,7 @@ mbedtls_lib_crypto-$(CONFIG_$(PHASE_)PKCS5_MBEDTLS) += \ $(MBEDTLS_LIB_DIR)/pkcs5.o mbedtls_lib_crypto-$(CONFIG_$(PHASE_)BLK_LUKS) += \ $(MBEDTLS_LIB_DIR)/aes.o \ + $(MBEDTLS_LIB_DIR)/base64.o \ $(MBEDTLS_LIB_DIR)/cipher.o \ $(MBEDTLS_LIB_DIR)/cipher_wrap.o diff --git a/lib/mbedtls/mbedtls_def_config.h b/lib/mbedtls/mbedtls_def_config.h index a0578d33ba6..d4e35ddeb61 100644 --- a/lib/mbedtls/mbedtls_def_config.h +++ b/lib/mbedtls/mbedtls_def_config.h @@ -65,6 +65,7 @@ #endif #if CONFIG_IS_ENABLED(BLK_LUKS) +#define MBEDTLS_BASE64_C #define MBEDTLS_CIPHER_C #define MBEDTLS_CIPHER_MODE_XTS #define MBEDTLS_AES_C -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Update ut_asserteq_str() and ut_asserteq_strn() to check for NULL. This allows tests to avoid doing this. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/test/ut.h | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/include/test/ut.h b/include/test/ut.h index 6731b43dba9..70eaaea5e0e 100644 --- a/include/test/ut.h +++ b/include/test/ut.h @@ -206,7 +206,15 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); const char *_val1 = (expr1), *_val2 = (expr2); \ int __ret = 0; \ \ - if (strcmp(_val1, _val2)) { \ + if (!_val1 || !_val2) { \ + ut_failf(uts, __FILE__, __LINE__, __func__, \ + #expr1 " = " #expr2, \ + "Expected \"%s\", got \"%s\"", \ + _val1 ? _val1 : "(null)", \ + _val2 ? _val2 : "(null)"); \ + if (!uts->soft_fail) \ + return CMD_RET_FAILURE; \ + } else if (strcmp(_val1, _val2)) { \ ut_failf(uts, __FILE__, __LINE__, __func__, \ #expr1 " = " #expr2, \ "Expected \"%s\", got \"%s\"", _val1, _val2); \ @@ -222,16 +230,26 @@ int ut_check_console_dump(struct unit_test_state *uts, int total_bytes); */ #define ut_asserteq_strn(expr1, expr2) ({ \ const char *_val1 = (expr1), *_val2 = (expr2); \ - int _len = strlen(_val1); \ int __ret = 0; \ \ - if (memcmp(_val1, _val2, _len)) { \ + if (!_val1 || !_val2) { \ ut_failf(uts, __FILE__, __LINE__, __func__, \ #expr1 " = " #expr2, \ - "Expected \"%.*s\", got \"%.*s\"", \ - _len, _val1, _len, _val2); \ + "Expected \"%s\", got \"%s\"", \ + _val1 ? _val1 : "(null)", \ + _val2 ? _val2 : "(null)"); \ if (!uts->soft_fail) \ return CMD_RET_FAILURE; \ + } else { \ + int _len = strlen(_val1); \ + if (memcmp(_val1, _val2, _len)) { \ + ut_failf(uts, __FILE__, __LINE__, __func__, \ + #expr1 " = " #expr2, \ + "Expected \"%.*s\", got \"%.*s\"", \ + _len, _val1, _len, _val2); \ + if (!uts->soft_fail) \ + return CMD_RET_FAILURE; \ + } \ } \ __ret; \ }) -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> JSON is a rather more free format than devicetree, so it is sometimes better to parse it into dtb format. This is widely used in U-Boot and we can use the ofnode interface to access it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- include/json.h | 34 +++ lib/json.c | 612 +++++++++++++++++++++++++++++++++++++++++++++++- test/lib/json.c | 337 +++++++++++++++++++++++++- 3 files changed, 981 insertions(+), 2 deletions(-) diff --git a/include/json.h b/include/json.h index 4d925e9db36..65c9bc5dcae 100644 --- a/include/json.h +++ b/include/json.h @@ -9,6 +9,8 @@ #ifndef __JSON_H__ #define __JSON_H__ +struct abuf; + /** * json_print_pretty() - Print JSON with indentation * @@ -20,4 +22,36 @@ */ void json_print_pretty(const char *json, int len); +/** + * json_to_fdt() - Convert JSON to a Flattened Device Tree (DTB) blob + * + * Parse a JSON string and convert it to a dtb blob. JSON objects become nodes; + * JSON properties become device tree properties. This is useful for converting + * LUKS2 metadata to a format that can be queried using U-Boot's ofnode APIs. + * + * This function temporarily modifies the JSON string in-place, writing nul + * terminators during parsing, then restores the original characters. The JSON + * string is only modified during the function call and is restored before + * returning. + * + * The resulting DTB contains copies of all data, so the JSON string can be + * freed or modified after this function returns. + * + * Conversion rules: + * - JSON objects → DT nodes + * - JSON strings → string properties + * - JSON numbers → u32 or u64 cell properties + * - JSON arrays of numbers → cell array properties (max MAX_ARRAY_SIZE) + * - JSON arrays of strings → stringlist properties (max MAX_ARRAY_SIZE) + * - JSON booleans → u32 properties (0 or 1). This breaks the dtb convention of + * simply using presence to indicate true, so that we can actually check + * what was present in the JSON data + * - JSON null → empty property + * + * @json: JSON string to parse (temporarily modified during call) + * @buf: abuf to init and populate with the DTB (called must uninit) + * Return: 0 on success, negative error code on failure + */ +int json_to_fdt(const char *json, struct abuf *buf); + #endif /* __JSON_H__ */ diff --git a/lib/json.c b/lib/json.c index 8cce042d631..3aad431d87b 100644 --- a/lib/json.c +++ b/lib/json.c @@ -1,13 +1,67 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * JSON pretty-printer + * JSON utilities including parser and devicetree converter * * Copyright (C) 2025 Canonical Ltd * Written by Simon Glass <simon.glass@canonical.com> */ +#include <abuf.h> #include <ctype.h> +#include <errno.h> #include <log.h> +#include <linux/libfdt.h> +#include <malloc.h> + +/* Maximum number of elements in a JSON array */ +#define MAX_ARRAY_SIZE 256 + +/* JSON token types */ +enum json_token_type { + JSONT_EOF = 0, + JSONT_LBRACE, /* { */ + JSONT_RBRACE, /* } */ + JSONT_LBRACKET, /* [ */ + JSONT_RBRACKET, /* ] */ + JSONT_COLON, /* : */ + JSONT_COMMA, /* , */ + JSONT_STRING, /* "string" */ + JSONT_NUMBER, /* 123 or 123.45 */ + JSONT_TRUE, /* true */ + JSONT_FALSE, /* false */ + JSONT_NULL, /* null */ + JSONT_ERROR +}; + +/** + * struct json_parser - JSON parser context + * @json: Input JSON string + * @pos: Current position in the input string + * @tok: Current token type + * @tok_start: Pointer to the start of the current token + * @tok_end: Pointer to the end of the current token + * @fdt: Flattened Device Tree buffer being constructed + * @fdt_size: Size of the FDT buffer in bytes + */ +struct json_parser { + const char *json; + const char *pos; + enum json_token_type tok; + const char *tok_start; + const char *tok_end; + void *fdt; + int fdt_size; +}; + +/* Increment position in JSON string */ +#define INC() ctx->pos++ + +/* Set token type */ +#define SET(t) ctx->tok = (t) + +/* Forward declarations for recursive parser functions */ +static int parse_object(struct json_parser *ctx, const char *node_name); +static int parse_value(struct json_parser *ctx, const char *prop_name); /** * print_indent() - Print indentation spaces @@ -120,3 +174,559 @@ void json_print_pretty(const char *json, int len) putc('\n'); } + +/** + * skip_whitespace() - Skip whitespace characters + * + * @ctx: Parser context + */ +static void skip_whitespace(struct json_parser *ctx) +{ + while (*ctx->pos && isspace(*ctx->pos)) + INC(); +} + +/** + * next_token() - Get the next JSON token + * + * @ctx: Parser context + * Return: token type + */ +static enum json_token_type next_token(struct json_parser *ctx) +{ + skip_whitespace(ctx); + + if (!*ctx->pos) { + SET(JSONT_EOF); + return JSONT_EOF; + } + + ctx->tok_start = ctx->pos; + + switch (*ctx->pos) { + case '{': + INC(); + SET(JSONT_LBRACE); + break; + case '}': + INC(); + SET(JSONT_RBRACE); + break; + case '[': + INC(); + SET(JSONT_LBRACKET); + break; + case ']': + INC(); + SET(JSONT_RBRACKET); + break; + case ':': + INC(); + SET(JSONT_COLON); + break; + case ',': + INC(); + SET(JSONT_COMMA); + break; + case '"': { + /* Parse string */ + INC(); + ctx->tok_start = ctx->pos; + while (*ctx->pos && *ctx->pos != '"') { + if (*ctx->pos == '\\' && ctx->pos[1]) + INC(); + INC(); + } + ctx->tok_end = ctx->pos; + if (*ctx->pos == '"') + INC(); + SET(JSONT_STRING); + break; + } + case '-': + case '0' ... '9': { + /* Parse number */ + if (*ctx->pos == '-') + INC(); + while (*ctx->pos && isdigit(*ctx->pos)) + INC(); + if (*ctx->pos == '.') { + INC(); + while (*ctx->pos && isdigit(*ctx->pos)) + INC(); + } + ctx->tok_end = ctx->pos; + SET(JSONT_NUMBER); + break; + } + case 't': + if (!strncmp(ctx->pos, "true", 4)) { + ctx->pos += 4; + ctx->tok_end = ctx->pos; + SET(JSONT_TRUE); + } else { + SET(JSONT_ERROR); + } + break; + case 'f': + if (!strncmp(ctx->pos, "false", 5)) { + ctx->pos += 5; + ctx->tok_end = ctx->pos; + SET(JSONT_FALSE); + } else { + SET(JSONT_ERROR); + } + break; + case 'n': + if (!strncmp(ctx->pos, "null", 4)) { + ctx->pos += 4; + ctx->tok_end = ctx->pos; + SET(JSONT_NULL); + } else { + SET(JSONT_ERROR); + } + break; + default: + SET(JSONT_ERROR); + break; + } + + return ctx->tok; +} + +/** + * parse_array() - Parse a JSON array + * + * @ctx: Parser context + * @prop: Property name for this array + * Return: 0 on success, negative error code on failure + */ +static int parse_array(struct json_parser *ctx, const char *prop) +{ + u32 values[MAX_ARRAY_SIZE]; + int index = 0; + int count = 0; + int ret; + + /* Expect [ */ + if (ctx->tok != JSONT_LBRACKET) + return -EINVAL; + + next_token(ctx); + + /* Handle empty array */ + if (ctx->tok == JSONT_RBRACKET) { + next_token(ctx); + return fdt_property(ctx->fdt, prop, NULL, 0); + } + + /* Check if this is an array of objects */ + if (ctx->tok == JSONT_LBRACE) { + /* Array of objects - create a subnode for each */ + while (ctx->tok != JSONT_RBRACKET) { + char name[64]; + + snprintf(name, sizeof(name), "%s-%d", prop, index++); + + ret = parse_object(ctx, name); + if (ret) + return ret; + + if (ctx->tok == JSONT_COMMA) + next_token(ctx); + else if (ctx->tok != JSONT_RBRACKET) + return -EINVAL; + } + + next_token(ctx); /* Skip ] */ + return 0; + } + + /* Array of primitives - collect into cell array */ + while (ctx->tok != JSONT_RBRACKET) { + if (ctx->tok == JSONT_NUMBER) { + char num_str[32]; + int len = min((int)(ctx->tok_end - ctx->tok_start), + (int)sizeof(num_str) - 1); + + if (count >= MAX_ARRAY_SIZE) + return -E2BIG; + + memcpy(num_str, ctx->tok_start, len); + num_str[len] = '\0'; + values[count++] = simple_strtoul(num_str, NULL, 0); + next_token(ctx); + } else if (ctx->tok == JSONT_STRING) { + /* String array - collect strings into a stringlist */ + char *strings[MAX_ARRAY_SIZE]; + char saved_chars[MAX_ARRAY_SIZE]; + int str_lens[MAX_ARRAY_SIZE]; + int count = 0; + int total_len = 0; + char *buf, *p; + int i; + + /* Collect all strings, temporarily nul-terminating */ + while (ctx->tok == JSONT_STRING) { + if (count >= MAX_ARRAY_SIZE) + return -E2BIG; + + strings[count] = (char *)ctx->tok_start; + saved_chars[count] = *ctx->tok_end; + *(char *)ctx->tok_end = '\0'; + str_lens[count] = strlen(strings[count]) + 1; + total_len += str_lens[count]; + count++; + + next_token(ctx); + + if (ctx->tok == JSONT_COMMA) + next_token(ctx); + else if (ctx->tok != JSONT_RBRACKET) + return -EINVAL; + } + + /* Build stringlist: concatenate all strings with nulls */ + buf = malloc(total_len); + if (!buf) + return -ENOMEM; + + p = buf; + for (i = 0; i < count; i++) { + memcpy(p, strings[i], str_lens[i]); + p += str_lens[i]; + } + + /* Create FDT stringlist property */ + ret = fdt_property(ctx->fdt, prop, buf, total_len); + free(buf); + + /* Restore all the saved characters */ + for (i = 0; i < count; i++) + strings[i][str_lens[i] - 1] = saved_chars[i]; + + if (ret) + return ret; + + next_token(ctx); + return 0; + } else { + return -EINVAL; + } + + if (ctx->tok == JSONT_COMMA) + next_token(ctx); + else if (ctx->tok != JSONT_RBRACKET) + return -EINVAL; + } + + next_token(ctx); /* Skip ] */ + + /* Write array as FDT property */ + if (count == 1) { + /* Single element - use fdt_property_u32() */ + ret = fdt_property_u32(ctx->fdt, prop, values[0]); + if (ret) + return ret; + } else if (count > 1) { + /* Multiple elements - convert to big-endian and write */ + u32 cells[MAX_ARRAY_SIZE]; + int i; + + for (i = 0; i < count; i++) + cells[i] = cpu_to_fdt32(values[i]); + + ret = fdt_property(ctx->fdt, prop, cells, count * sizeof(u32)); + if (ret) + return ret; + } + + return 0; +} + +/** + * parse_value() - Parse a JSON value + * + * @ctx: Parser context + * @prop_name: Property name for this value + * Return: 0 on success, negative error code on failure + */ +static int parse_value(struct json_parser *ctx, const char *prop_name) +{ + int ret; + + switch (ctx->tok) { + case JSONT_STRING: { + char str[256]; + int len = min((int)(ctx->tok_end - ctx->tok_start), + (int)sizeof(str) - 1); + + memcpy(str, ctx->tok_start, len); + str[len] = '\0'; + + ret = fdt_property_string(ctx->fdt, prop_name, str); + if (ret) + return ret; + + next_token(ctx); + break; + } + case JSONT_NUMBER: { + char num_str[32]; + u64 val; + int len = min((int)(ctx->tok_end - ctx->tok_start), + (int)sizeof(num_str) - 1); + + memcpy(num_str, ctx->tok_start, len); + num_str[len] = '\0'; + + val = simple_strtoull(num_str, NULL, 0); + + /* Use u32 if it fits, otherwise u64 */ + if (val <= 0xffffffff) + ret = fdt_property_u32(ctx->fdt, prop_name, (u32)val); + else + ret = fdt_property_u64(ctx->fdt, prop_name, val); + + if (ret) + return ret; + + next_token(ctx); + break; + } + case JSONT_TRUE: + ret = fdt_property_u32(ctx->fdt, prop_name, 1); + if (ret) + return ret; + next_token(ctx); + break; + case JSONT_FALSE: + ret = fdt_property_u32(ctx->fdt, prop_name, 0); + if (ret) + return ret; + next_token(ctx); + break; + case JSONT_NULL: + ret = fdt_property(ctx->fdt, prop_name, NULL, 0); + if (ret) + return ret; + next_token(ctx); + break; + case JSONT_LBRACE: + /* Nested object */ + ret = parse_object(ctx, prop_name); + if (ret) + return ret; + break; + case JSONT_LBRACKET: + /* Array */ + ret = parse_array(ctx, prop_name); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * parse_object() - Parse a JSON object + * + * @ctx: Parser context + * @node_name: Name for the device tree node (NULL for root) + * Return: 0 on success, negative error code on failure + */ +static int parse_object(struct json_parser *ctx, const char *node_name) +{ + int ret; + + /* Expect { */ + if (ctx->tok != JSONT_LBRACE) + return -EINVAL; + + /* Begin device tree node */ + if (node_name) { + ret = fdt_begin_node(ctx->fdt, node_name); + if (ret) + return ret; + } + + next_token(ctx); + + /* Handle empty object */ + if (ctx->tok == JSONT_RBRACE) { + next_token(ctx); + if (node_name) { + ret = fdt_end_node(ctx->fdt); + if (ret) + return ret; + } + return 0; + } + + /* Parse key-value pairs */ + while (ctx->tok != JSONT_RBRACE) { + char key[128]; + int len; + + /* Expect string key */ + if (ctx->tok != JSONT_STRING) + return -EINVAL; + + len = min((int)(ctx->tok_end - ctx->tok_start), + (int)sizeof(key) - 1); + memcpy(key, ctx->tok_start, len); + key[len] = '\0'; + + next_token(ctx); + + /* Expect : */ + if (ctx->tok != JSONT_COLON) + return -EINVAL; + + next_token(ctx); + + /* Parse value */ + ret = parse_value(ctx, key); + if (ret) + return ret; + + /* Expect , or } */ + if (ctx->tok == JSONT_COMMA) + next_token(ctx); + else if (ctx->tok != JSONT_RBRACE) + return -EINVAL; + } + + next_token(ctx); /* Skip } */ + + /* End device tree node */ + if (node_name) { + ret = fdt_end_node(ctx->fdt); + if (ret) + return ret; + } + + return 0; +} + +/** + * parse_json_root() - Parse JSON and create FDT + * + * @ctx: Parser context (must be initialized with FDT buffer) + * Return: 0 on success, negative error code on failure + */ +static int parse_json_root(struct json_parser *ctx) +{ + int ret; + + /* Initialize FDT */ + ret = fdt_create(ctx->fdt, ctx->fdt_size); + if (ret) + return ret; + + ret = fdt_finish_reservemap(ctx->fdt); + if (ret) + return ret; + + /* Begin root node */ + ret = fdt_begin_node(ctx->fdt, ""); + if (ret) + return ret; + + /* Get first token */ + next_token(ctx); + + /* Parse the JSON (expecting an object at the root) */ + if (ctx->tok == JSONT_LBRACE) { + /* Parse the root's contents directly into the root node */ + next_token(ctx); + + while (ctx->tok != JSONT_RBRACE) { + char key[128]; + int len; + + if (ctx->tok != JSONT_STRING) + return -EINVAL; + + len = min((int)(ctx->tok_end - ctx->tok_start), + (int)sizeof(key) - 1); + memcpy(key, ctx->tok_start, len); + key[len] = '\0'; + + next_token(ctx); + + if (ctx->tok != JSONT_COLON) + return -EINVAL; + + next_token(ctx); + + ret = parse_value(ctx, key); + if (ret) + return ret; + + if (ctx->tok == JSONT_COMMA) + next_token(ctx); + else if (ctx->tok != JSONT_RBRACE) + return -EINVAL; + } + } else { + return -EINVAL; + } + + /* End root node */ + ret = fdt_end_node(ctx->fdt); + if (ret) + return ret; + + /* Finalize FDT */ + ret = fdt_finish(ctx->fdt); + if (ret) + return ret; + + return 0; +} + +int json_to_fdt(const char *json, struct abuf *buf) +{ + struct json_parser ctx; + void *fdt_buf; + int fdt_size; + int ret; + + if (!json || !buf) + return -EINVAL; + + /* Estimate FDT size: JSON length * 2 should be plenty */ + fdt_size = max((int)strlen(json) * 2, 4096); + + abuf_init(buf); + if (!abuf_realloc(buf, fdt_size)) + return -ENOMEM; + + fdt_buf = abuf_data(buf); + + /* Init parser */ + memset(&ctx, 0, sizeof(ctx)); + ctx.json = json; + ctx.pos = json; + ctx.fdt = fdt_buf; + ctx.fdt_size = fdt_size; + + /* Parse JSON and create FDT */ + ret = parse_json_root(&ctx); + if (ret) + goto err; + + /* Adjust abuf size to actual FDT size */ + abuf_realloc(buf, fdt_totalsize(fdt_buf)); + log_debug("json %ld buf %ld\n", strlen(json), buf->size); + + return 0; + +err: + abuf_uninit(buf); + return ret; +} diff --git a/test/lib/json.c b/test/lib/json.c index 76afeb9b241..0cddddcb2a3 100644 --- a/test/lib/json.c +++ b/test/lib/json.c @@ -1,12 +1,16 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Tests for JSON pretty-printer + * Tests for JSON utilities including parser and FDT converter * * Copyright (C) 2025 Canonical Ltd * Written by Simon Glass <simon.glass@canonical.com> */ +#include <dm/ofnode.h> +#include <fdt_support.h> +#include <image.h> #include <json.h> +#include <linux/libfdt.h> #include <test/lib.h> #include <test/test.h> #include <test/ut.h> @@ -209,3 +213,334 @@ static int lib_test_json_whitespace(struct unit_test_state *uts) return 0; } LIB_TEST(lib_test_json_whitespace, UTF_CONSOLE); + +/* JSON to FDT conversion tests */ + +static int lib_test_json_to_fdt_simple(struct unit_test_state *uts) +{ + const char *json = "{\"name\":\"test\",\"value\":42}"; + struct abuf buf; + void *fdt_buf; + + ut_assertok(json_to_fdt(json, &buf)); + + fdt_buf = abuf_data(&buf); + + /* Verify FDT is valid */ + ut_assertok(fdt_check_header(fdt_buf)); + + /* Check string property */ + ut_asserteq_str("test", fdt_getprop(fdt_buf, 0, "name", NULL)); + + /* Check integer property */ + ut_asserteq(42, fdtdec_get_int(fdt_buf, 0, "value", 0)); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_simple, 0); + +static int lib_test_json_to_fdt_nested(struct unit_test_state *uts) +{ + const char *json = "{\"outer\":{\"inner\":\"value\"}}"; + struct abuf buf; + void *fdt_buf; + int node; + + ut_assertok(json_to_fdt(json, &buf)); + + fdt_buf = abuf_data(&buf); + + /* Verify FDT is valid */ + ut_assertok(fdt_check_header(fdt_buf)); + + /* Find nested node */ + node = fdt_path_offset(fdt_buf, "/outer"); + ut_assert(node >= 0); + + /* Check property in nested node */ + ut_asserteq_str("value", fdt_getprop(fdt_buf, node, "inner", NULL)); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_nested, 0); + +static int lib_test_json_to_fdt_array(struct unit_test_state *uts) +{ + const char *json = "{\"numbers\":[1,2,3]}"; + struct abuf buf; + void *fdt_buf; + u32 arr[8]; + int size; + oftree tree; + ofnode root; + + ut_assertok(json_to_fdt(json, &buf)); + + fdt_buf = abuf_data(&buf); + + /* Verify FDT is valid */ + ut_assertok(fdt_check_header(fdt_buf)); + + /* Create oftree from FDT */ + tree = oftree_from_fdt(fdt_buf); + ut_assert(oftree_valid(tree)); + + root = oftree_root(tree); + ut_assert(ofnode_valid(root)); + + /* Check array property */ + ut_assertnonnull(ofnode_get_property(root, "numbers", &size)); + size /= sizeof(u32); + ut_asserteq(3, size); + ut_assertok(ofnode_read_u32_array(root, "numbers", arr, size)); + ut_asserteq(1, arr[0]); + ut_asserteq(2, arr[1]); + ut_asserteq(3, arr[2]); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_array, 0); + +static int lib_test_json_to_fdt_string_array(struct unit_test_state *uts) +{ + char json[] = "{\"tags\":[\"first\",\"second\",\"third\"]}"; + struct abuf buf; + void *fdt_buf; + const char *str; + ofnode root; + oftree tree; + + ut_assertok(json_to_fdt(json, &buf)); + + fdt_buf = abuf_data(&buf); + + /* Verify FDT is valid */ + ut_assertok(fdt_check_header(fdt_buf)); + + /* Create oftree from FDT */ + tree = oftree_from_fdt(fdt_buf); + ut_assert(oftree_valid(tree)); + + root = oftree_root(tree); + ut_assert(ofnode_valid(root)); + + /* Check string array property */ + ut_assertok(ofnode_read_string_index(root, "tags", 0, &str)); + ut_asserteq_str("first", str); + ut_assertok(ofnode_read_string_index(root, "tags", 1, &str)); + ut_asserteq_str("second", str); + ut_assertok(ofnode_read_string_index(root, "tags", 2, &str)); + ut_asserteq_str("third", str); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_string_array, 0); + +static int lib_test_json_to_fdt_bool(struct unit_test_state *uts) +{ + const char *json = "{\"enabled\":true,\"disabled\":false}"; + struct abuf buf; + void *fdt_buf; + + ut_assertok(json_to_fdt(json, &buf)); + + fdt_buf = abuf_data(&buf); + + /* Verify FDT is valid */ + ut_assertok(fdt_check_header(fdt_buf)); + + /* Check boolean properties */ + ut_asserteq(1, fdtdec_get_int(fdt_buf, 0, "enabled", 0)); + ut_asserteq(0, fdtdec_get_int(fdt_buf, 0, "disabled", 0)); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_bool, 0); + +/* Test with realistic LUKS2 JSON metadata using ofnode API */ +static int lib_test_json_to_fdt_luks2(struct unit_test_state *uts) +{ + /* Simplified LUKS2 JSON metadata structure */ + const char *luks2_json = + "{" + " \"keyslots\": {" + " \"0\": {" + " \"type\": \"luks2\"," + " \"key_size\": 32," + " \"area\": {" + " \"type\": \"raw\"," + " \"offset\": \"32768\"," + " \"size\": \"258048\"" + " }," + " \"kdf\": {" + " \"type\": \"pbkdf2\"," + " \"hash\": \"sha256\"," + " \"iterations\": 1000," + " \"salt\": \"aGVsbG93b3JsZA==\"" + " }" + " }," + " \"1\": {" + " \"type\": \"luks2\"," + " \"key_size\": 32," + " \"area\": {" + " \"type\": \"raw\"," + " \"offset\": \"290816\"," + " \"size\": \"258048\"" + " }," + " \"kdf\": {" + " \"type\": \"pbkdf2\"," + " \"hash\": \"sha256\"," + " \"iterations\": 2000," + " \"salt\": \"YW5vdGhlcnNhbHQ=\"" + " }" + " }" + " }," + " \"segments\": {" + " \"0\": {" + " \"type\": \"crypt\"," + " \"offset\": \"16777216\"," + " \"size\": \"dynamic\"," + " \"iv_tweak\": \"0\"," + " \"encryption\": \"aes-cbc-essiv:sha256\"," + " \"sector_size\": 512" + " }" + " }," + " \"digests\": {" + " \"0\": {" + " \"type\": \"pbkdf2\"," + " \"keyslots\": [0, 1]," + " \"segments\": [0]," + " \"hash\": \"sha256\"," + " \"iterations\": 1000," + " \"salt\": \"c2FsdHlzYWx0\"" + " }" + " }," + " \"config\": {" + " \"json_size\": \"12288\"," + " \"keyslots_size\": \"3145728\"" + " }" + "}"; + + ofnode segments, segment0, digests, digest0, config; + ofnode root, keyslots, keyslot0, kdf; + struct abuf buf; + u32 arr[8]; + int size; + void *fdt_buf; + oftree tree; + + ut_assertok(json_to_fdt(luks2_json, &buf)); + + /* Verify FDT is valid */ + fdt_buf = abuf_data(&buf); + ut_assertok(fdt_check_header(fdt_buf)); + + /* Create oftree from FDT */ + tree = oftree_from_fdt(fdt_buf); + ut_assert(oftree_valid(tree)); + + /* Get root node */ + root = oftree_root(tree); + ut_assert(ofnode_valid(root)); + + /* Navigate to keyslots node */ + keyslots = ofnode_find_subnode(root, "keyslots"); + ut_assert(ofnode_valid(keyslots)); + + /* Navigate to keyslot 0 */ + keyslot0 = ofnode_find_subnode(keyslots, "0"); + ut_assert(ofnode_valid(keyslot0)); + + /* Check keyslot type */ + ut_asserteq_str("luks2", ofnode_read_string(keyslot0, "type")); + + /* Check key_size */ + ut_asserteq(32, ofnode_read_u32_default(keyslot0, "key_size", 0)); + + /* Navigate to KDF node */ + kdf = ofnode_find_subnode(keyslot0, "kdf"); + ut_assert(ofnode_valid(kdf)); + + /* Check KDF type */ + ut_asserteq_str("pbkdf2", ofnode_read_string(kdf, "type")); + + /* Check KDF hash */ + ut_asserteq_str("sha256", ofnode_read_string(kdf, "hash")); + + /* Check iterations */ + ut_asserteq(1000, ofnode_read_u32_default(kdf, "iterations", 0)); + + /* Check salt (base64 string) */ + ut_asserteq_str("aGVsbG93b3JsZA==", ofnode_read_string(kdf, "salt")); + + /* Navigate to segments node */ + segments = ofnode_find_subnode(root, "segments"); + ut_assert(ofnode_valid(segments)); + + /* Navigate to segment 0 */ + segment0 = ofnode_find_subnode(segments, "0"); + ut_assert(ofnode_valid(segment0)); + + /* Check segment type */ + ut_asserteq_str("crypt", ofnode_read_string(segment0, "type")); + + /* Check encryption */ + ut_asserteq_str("aes-cbc-essiv:sha256", ofnode_read_string(segment0, "encryption")); + + /* Check offset (stored as string in JSON) */ + ut_asserteq_str("16777216", ofnode_read_string(segment0, "offset")); + + /* Check sector_size */ + ut_asserteq(512, ofnode_read_u32_default(segment0, "sector_size", 0)); + + /* Navigate to digests node */ + digests = ofnode_find_subnode(root, "digests"); + ut_assert(ofnode_valid(digests)); + + /* Navigate to digest 0 */ + digest0 = ofnode_find_subnode(digests, "0"); + ut_assert(ofnode_valid(digest0)); + + /* Check digest type */ + ut_asserteq_str("pbkdf2", ofnode_read_string(digest0, "type")); + + /* Check keyslots array */ + ut_assertnonnull(ofnode_get_property(digest0, "keyslots", &size)); + size /= sizeof(u32); + ut_asserteq(2, size); + ut_assertok(ofnode_read_u32_array(digest0, "keyslots", arr, size)); + ut_asserteq(0, arr[0]); + ut_asserteq(1, arr[1]); + + /* Check segments array */ + ut_assertnonnull(ofnode_get_property(digest0, "segments", &size)); + ut_asserteq(4, size); + size /= sizeof(u32); + ut_assertok(ofnode_read_u32_array(digest0, "segments", arr, size)); + ut_asserteq(0, arr[0]); + + /* Navigate to config node */ + config = ofnode_find_subnode(root, "config"); + ut_assert(ofnode_valid(config)); + + /* Check json_size (stored as string in JSON) */ + ut_asserteq_str("12288", ofnode_read_string(config, "json_size")); + + /* Check keyslots_size (stored as string in JSON) */ + ut_asserteq_str("3145728", ofnode_read_string(config, "keyslots_size")); + + abuf_uninit(&buf); + + return 0; +} +LIB_TEST(lib_test_json_to_fdt_luks2, 0); -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> This library is used for full-disk encryption with LUKS, so bring it in from https://github.com/P-H-C/phc-winner-argon2 commit: f57e61e Merge pull request #321 from bittorf/fix-spelling-mistakes Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- lib/argon2/argon2.c | 452 +++++++++++++++++++ lib/argon2/argon2.h | 437 ++++++++++++++++++ lib/argon2/blake2/blake2-impl.h | 156 +++++++ lib/argon2/blake2/blake2.h | 89 ++++ lib/argon2/blake2/blake2b.c | 390 ++++++++++++++++ lib/argon2/blake2/blamka-round-ref.h | 56 +++ lib/argon2/core.c | 648 +++++++++++++++++++++++++++ lib/argon2/core.h | 228 ++++++++++ lib/argon2/ref.c | 194 ++++++++ 9 files changed, 2650 insertions(+) create mode 100644 lib/argon2/argon2.c create mode 100644 lib/argon2/argon2.h create mode 100644 lib/argon2/blake2/blake2-impl.h create mode 100644 lib/argon2/blake2/blake2.h create mode 100644 lib/argon2/blake2/blake2b.c create mode 100644 lib/argon2/blake2/blamka-round-ref.h create mode 100644 lib/argon2/core.c create mode 100644 lib/argon2/core.h create mode 100644 lib/argon2/ref.c diff --git a/lib/argon2/argon2.c b/lib/argon2/argon2.c new file mode 100644 index 00000000000..34da3d6b4ac --- /dev/null +++ b/lib/argon2/argon2.c @@ -0,0 +1,452 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include "argon2.h" +#include "encoding.h" +#include "core.h" + +const char *argon2_type2string(argon2_type type, int uppercase) { + switch (type) { + case Argon2_d: + return uppercase ? "Argon2d" : "argon2d"; + case Argon2_i: + return uppercase ? "Argon2i" : "argon2i"; + case Argon2_id: + return uppercase ? "Argon2id" : "argon2id"; + } + + return NULL; +} + +int argon2_ctx(argon2_context *context, argon2_type type) { + /* 1. Validate all inputs */ + int result = validate_inputs(context); + uint32_t memory_blocks, segment_length; + argon2_instance_t instance; + + if (ARGON2_OK != result) { + return result; + } + + if (Argon2_d != type && Argon2_i != type && Argon2_id != type) { + return ARGON2_INCORRECT_TYPE; + } + + /* 2. Align memory size */ + /* Minimum memory_blocks = 8L blocks, where L is the number of lanes */ + memory_blocks = context->m_cost; + + if (memory_blocks < 2 * ARGON2_SYNC_POINTS * context->lanes) { + memory_blocks = 2 * ARGON2_SYNC_POINTS * context->lanes; + } + + segment_length = memory_blocks / (context->lanes * ARGON2_SYNC_POINTS); + /* Ensure that all segments have equal length */ + memory_blocks = segment_length * (context->lanes * ARGON2_SYNC_POINTS); + + instance.version = context->version; + instance.memory = NULL; + instance.passes = context->t_cost; + instance.memory_blocks = memory_blocks; + instance.segment_length = segment_length; + instance.lane_length = segment_length * ARGON2_SYNC_POINTS; + instance.lanes = context->lanes; + instance.threads = context->threads; + instance.type = type; + + if (instance.threads > instance.lanes) { + instance.threads = instance.lanes; + } + + /* 3. Initialization: Hashing inputs, allocating memory, filling first + * blocks + */ + result = initialize(&instance, context); + + if (ARGON2_OK != result) { + return result; + } + + /* 4. Filling memory */ + result = fill_memory_blocks(&instance); + + if (ARGON2_OK != result) { + return result; + } + /* 5. Finalization */ + finalize(context, &instance); + + return ARGON2_OK; +} + +int argon2_hash(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, const size_t saltlen, + void *hash, const size_t hashlen, char *encoded, + const size_t encodedlen, argon2_type type, + const uint32_t version){ + + argon2_context context; + int result; + uint8_t *out; + + if (pwdlen > ARGON2_MAX_PWD_LENGTH) { + return ARGON2_PWD_TOO_LONG; + } + + if (saltlen > ARGON2_MAX_SALT_LENGTH) { + return ARGON2_SALT_TOO_LONG; + } + + if (hashlen > ARGON2_MAX_OUTLEN) { + return ARGON2_OUTPUT_TOO_LONG; + } + + if (hashlen < ARGON2_MIN_OUTLEN) { + return ARGON2_OUTPUT_TOO_SHORT; + } + + out = malloc(hashlen); + if (!out) { + return ARGON2_MEMORY_ALLOCATION_ERROR; + } + + context.out = (uint8_t *)out; + context.outlen = (uint32_t)hashlen; + context.pwd = CONST_CAST(uint8_t *)pwd; + context.pwdlen = (uint32_t)pwdlen; + context.salt = CONST_CAST(uint8_t *)salt; + context.saltlen = (uint32_t)saltlen; + context.secret = NULL; + context.secretlen = 0; + context.ad = NULL; + context.adlen = 0; + context.t_cost = t_cost; + context.m_cost = m_cost; + context.lanes = parallelism; + context.threads = parallelism; + context.allocate_cbk = NULL; + context.free_cbk = NULL; + context.flags = ARGON2_DEFAULT_FLAGS; + context.version = version; + + result = argon2_ctx(&context, type); + + if (result != ARGON2_OK) { + clear_internal_memory(out, hashlen); + free(out); + return result; + } + + /* if raw hash requested, write it */ + if (hash) { + memcpy(hash, out, hashlen); + } + + /* if encoding requested, write it */ + if (encoded && encodedlen) { + if (encode_string(encoded, encodedlen, &context, type) != ARGON2_OK) { + clear_internal_memory(out, hashlen); /* wipe buffers if error */ + clear_internal_memory(encoded, encodedlen); + free(out); + return ARGON2_ENCODING_FAIL; + } + } + clear_internal_memory(out, hashlen); + free(out); + + return ARGON2_OK; +} + +int argon2i_hash_encoded(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, const size_t hashlen, + char *encoded, const size_t encodedlen) { + + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + NULL, hashlen, encoded, encodedlen, Argon2_i, + ARGON2_VERSION_NUMBER); +} + +int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, const size_t hashlen) { + + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + hash, hashlen, NULL, 0, Argon2_i, ARGON2_VERSION_NUMBER); +} + +int argon2d_hash_encoded(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, const size_t hashlen, + char *encoded, const size_t encodedlen) { + + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + NULL, hashlen, encoded, encodedlen, Argon2_d, + ARGON2_VERSION_NUMBER); +} + +int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, const size_t hashlen) { + + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + hash, hashlen, NULL, 0, Argon2_d, ARGON2_VERSION_NUMBER); +} + +int argon2id_hash_encoded(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, const size_t hashlen, + char *encoded, const size_t encodedlen) { + + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + NULL, hashlen, encoded, encodedlen, Argon2_id, + ARGON2_VERSION_NUMBER); +} + +int argon2id_hash_raw(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, const size_t hashlen) { + return argon2_hash(t_cost, m_cost, parallelism, pwd, pwdlen, salt, saltlen, + hash, hashlen, NULL, 0, Argon2_id, + ARGON2_VERSION_NUMBER); +} + +static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) { + size_t i; + uint8_t d = 0U; + + for (i = 0U; i < len; i++) { + d |= b1[i] ^ b2[i]; + } + return (int)((1 & ((d - 1) >> 8)) - 1); +} + +int argon2_verify(const char *encoded, const void *pwd, const size_t pwdlen, + argon2_type type) { + + argon2_context ctx; + uint8_t *desired_result = NULL; + + int ret = ARGON2_OK; + + size_t encoded_len; + uint32_t max_field_len; + + if (pwdlen > ARGON2_MAX_PWD_LENGTH) { + return ARGON2_PWD_TOO_LONG; + } + + if (encoded == NULL) { + return ARGON2_DECODING_FAIL; + } + + encoded_len = strlen(encoded); + if (encoded_len > UINT32_MAX) { + return ARGON2_DECODING_FAIL; + } + + /* No field can be longer than the encoded length */ + max_field_len = (uint32_t)encoded_len; + + ctx.saltlen = max_field_len; + ctx.outlen = max_field_len; + + ctx.salt = malloc(ctx.saltlen); + ctx.out = malloc(ctx.outlen); + if (!ctx.salt || !ctx.out) { + ret = ARGON2_MEMORY_ALLOCATION_ERROR; + goto fail; + } + + ctx.pwd = (uint8_t *)pwd; + ctx.pwdlen = (uint32_t)pwdlen; + + ret = decode_string(&ctx, encoded, type); + if (ret != ARGON2_OK) { + goto fail; + } + + /* Set aside the desired result, and get a new buffer. */ + desired_result = ctx.out; + ctx.out = malloc(ctx.outlen); + if (!ctx.out) { + ret = ARGON2_MEMORY_ALLOCATION_ERROR; + goto fail; + } + + ret = argon2_verify_ctx(&ctx, (char *)desired_result, type); + if (ret != ARGON2_OK) { + goto fail; + } + +fail: + free(ctx.salt); + free(ctx.out); + free(desired_result); + + return ret; +} + +int argon2i_verify(const char *encoded, const void *pwd, const size_t pwdlen) { + + return argon2_verify(encoded, pwd, pwdlen, Argon2_i); +} + +int argon2d_verify(const char *encoded, const void *pwd, const size_t pwdlen) { + + return argon2_verify(encoded, pwd, pwdlen, Argon2_d); +} + +int argon2id_verify(const char *encoded, const void *pwd, const size_t pwdlen) { + + return argon2_verify(encoded, pwd, pwdlen, Argon2_id); +} + +int argon2d_ctx(argon2_context *context) { + return argon2_ctx(context, Argon2_d); +} + +int argon2i_ctx(argon2_context *context) { + return argon2_ctx(context, Argon2_i); +} + +int argon2id_ctx(argon2_context *context) { + return argon2_ctx(context, Argon2_id); +} + +int argon2_verify_ctx(argon2_context *context, const char *hash, + argon2_type type) { + int ret = argon2_ctx(context, type); + if (ret != ARGON2_OK) { + return ret; + } + + if (argon2_compare((uint8_t *)hash, context->out, context->outlen)) { + return ARGON2_VERIFY_MISMATCH; + } + + return ARGON2_OK; +} + +int argon2d_verify_ctx(argon2_context *context, const char *hash) { + return argon2_verify_ctx(context, hash, Argon2_d); +} + +int argon2i_verify_ctx(argon2_context *context, const char *hash) { + return argon2_verify_ctx(context, hash, Argon2_i); +} + +int argon2id_verify_ctx(argon2_context *context, const char *hash) { + return argon2_verify_ctx(context, hash, Argon2_id); +} + +const char *argon2_error_message(int error_code) { + switch (error_code) { + case ARGON2_OK: + return "OK"; + case ARGON2_OUTPUT_PTR_NULL: + return "Output pointer is NULL"; + case ARGON2_OUTPUT_TOO_SHORT: + return "Output is too short"; + case ARGON2_OUTPUT_TOO_LONG: + return "Output is too long"; + case ARGON2_PWD_TOO_SHORT: + return "Password is too short"; + case ARGON2_PWD_TOO_LONG: + return "Password is too long"; + case ARGON2_SALT_TOO_SHORT: + return "Salt is too short"; + case ARGON2_SALT_TOO_LONG: + return "Salt is too long"; + case ARGON2_AD_TOO_SHORT: + return "Associated data is too short"; + case ARGON2_AD_TOO_LONG: + return "Associated data is too long"; + case ARGON2_SECRET_TOO_SHORT: + return "Secret is too short"; + case ARGON2_SECRET_TOO_LONG: + return "Secret is too long"; + case ARGON2_TIME_TOO_SMALL: + return "Time cost is too small"; + case ARGON2_TIME_TOO_LARGE: + return "Time cost is too large"; + case ARGON2_MEMORY_TOO_LITTLE: + return "Memory cost is too small"; + case ARGON2_MEMORY_TOO_MUCH: + return "Memory cost is too large"; + case ARGON2_LANES_TOO_FEW: + return "Too few lanes"; + case ARGON2_LANES_TOO_MANY: + return "Too many lanes"; + case ARGON2_PWD_PTR_MISMATCH: + return "Password pointer is NULL, but password length is not 0"; + case ARGON2_SALT_PTR_MISMATCH: + return "Salt pointer is NULL, but salt length is not 0"; + case ARGON2_SECRET_PTR_MISMATCH: + return "Secret pointer is NULL, but secret length is not 0"; + case ARGON2_AD_PTR_MISMATCH: + return "Associated data pointer is NULL, but ad length is not 0"; + case ARGON2_MEMORY_ALLOCATION_ERROR: + return "Memory allocation error"; + case ARGON2_FREE_MEMORY_CBK_NULL: + return "The free memory callback is NULL"; + case ARGON2_ALLOCATE_MEMORY_CBK_NULL: + return "The allocate memory callback is NULL"; + case ARGON2_INCORRECT_PARAMETER: + return "Argon2_Context context is NULL"; + case ARGON2_INCORRECT_TYPE: + return "There is no such version of Argon2"; + case ARGON2_OUT_PTR_MISMATCH: + return "Output pointer mismatch"; + case ARGON2_THREADS_TOO_FEW: + return "Not enough threads"; + case ARGON2_THREADS_TOO_MANY: + return "Too many threads"; + case ARGON2_MISSING_ARGS: + return "Missing arguments"; + case ARGON2_ENCODING_FAIL: + return "Encoding failed"; + case ARGON2_DECODING_FAIL: + return "Decoding failed"; + case ARGON2_THREAD_FAIL: + return "Threading failure"; + case ARGON2_DECODING_LENGTH_FAIL: + return "Some of encoded parameters are too long or too short"; + case ARGON2_VERIFY_MISMATCH: + return "The password does not match the supplied hash"; + default: + return "Unknown error code"; + } +} + +size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, uint32_t parallelism, + uint32_t saltlen, uint32_t hashlen, argon2_type type) { + return strlen("$$v=$m=,t=,p=$$") + strlen(argon2_type2string(type, 0)) + + numlen(t_cost) + numlen(m_cost) + numlen(parallelism) + + b64len(saltlen) + b64len(hashlen) + numlen(ARGON2_VERSION_NUMBER) + 1; +} diff --git a/lib/argon2/argon2.h b/lib/argon2/argon2.h new file mode 100644 index 00000000000..3980bb352f2 --- /dev/null +++ b/lib/argon2/argon2.h @@ -0,0 +1,437 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef ARGON2_H +#define ARGON2_H + +#include <stdint.h> +#include <stddef.h> +#include <limits.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +/* Symbols visibility control */ +#ifdef A2_VISCTL +#define ARGON2_PUBLIC __attribute__((visibility("default"))) +#define ARGON2_LOCAL __attribute__ ((visibility ("hidden"))) +#elif defined(_MSC_VER) +#define ARGON2_PUBLIC __declspec(dllexport) +#define ARGON2_LOCAL +#else +#define ARGON2_PUBLIC +#define ARGON2_LOCAL +#endif + +/* + * Argon2 input parameter restrictions + */ + +/* Minimum and maximum number of lanes (degree of parallelism) */ +#define ARGON2_MIN_LANES UINT32_C(1) +#define ARGON2_MAX_LANES UINT32_C(0xFFFFFF) + +/* Minimum and maximum number of threads */ +#define ARGON2_MIN_THREADS UINT32_C(1) +#define ARGON2_MAX_THREADS UINT32_C(0xFFFFFF) + +/* Number of synchronization points between lanes per pass */ +#define ARGON2_SYNC_POINTS UINT32_C(4) + +/* Minimum and maximum digest size in bytes */ +#define ARGON2_MIN_OUTLEN UINT32_C(4) +#define ARGON2_MAX_OUTLEN UINT32_C(0xFFFFFFFF) + +/* Minimum and maximum number of memory blocks (each of BLOCK_SIZE bytes) */ +#define ARGON2_MIN_MEMORY (2 * ARGON2_SYNC_POINTS) /* 2 blocks per slice */ + +#define ARGON2_MIN(a, b) ((a) < (b) ? (a) : (b)) +/* Max memory size is addressing-space/2, topping at 2^32 blocks (4 TB) */ +#define ARGON2_MAX_MEMORY_BITS \ + ARGON2_MIN(UINT32_C(32), (sizeof(void *) * CHAR_BIT - 10 - 1)) +#define ARGON2_MAX_MEMORY \ + ARGON2_MIN(UINT32_C(0xFFFFFFFF), UINT64_C(1) << ARGON2_MAX_MEMORY_BITS) + +/* Minimum and maximum number of passes */ +#define ARGON2_MIN_TIME UINT32_C(1) +#define ARGON2_MAX_TIME UINT32_C(0xFFFFFFFF) + +/* Minimum and maximum password length in bytes */ +#define ARGON2_MIN_PWD_LENGTH UINT32_C(0) +#define ARGON2_MAX_PWD_LENGTH UINT32_C(0xFFFFFFFF) + +/* Minimum and maximum associated data length in bytes */ +#define ARGON2_MIN_AD_LENGTH UINT32_C(0) +#define ARGON2_MAX_AD_LENGTH UINT32_C(0xFFFFFFFF) + +/* Minimum and maximum salt length in bytes */ +#define ARGON2_MIN_SALT_LENGTH UINT32_C(8) +#define ARGON2_MAX_SALT_LENGTH UINT32_C(0xFFFFFFFF) + +/* Minimum and maximum key length in bytes */ +#define ARGON2_MIN_SECRET UINT32_C(0) +#define ARGON2_MAX_SECRET UINT32_C(0xFFFFFFFF) + +/* Flags to determine which fields are securely wiped (default = no wipe). */ +#define ARGON2_DEFAULT_FLAGS UINT32_C(0) +#define ARGON2_FLAG_CLEAR_PASSWORD (UINT32_C(1) << 0) +#define ARGON2_FLAG_CLEAR_SECRET (UINT32_C(1) << 1) + +/* Global flag to determine if we are wiping internal memory buffers. This flag + * is defined in core.c and defaults to 1 (wipe internal memory). */ +extern int FLAG_clear_internal_memory; + +/* Error codes */ +typedef enum Argon2_ErrorCodes { + ARGON2_OK = 0, + + ARGON2_OUTPUT_PTR_NULL = -1, + + ARGON2_OUTPUT_TOO_SHORT = -2, + ARGON2_OUTPUT_TOO_LONG = -3, + + ARGON2_PWD_TOO_SHORT = -4, + ARGON2_PWD_TOO_LONG = -5, + + ARGON2_SALT_TOO_SHORT = -6, + ARGON2_SALT_TOO_LONG = -7, + + ARGON2_AD_TOO_SHORT = -8, + ARGON2_AD_TOO_LONG = -9, + + ARGON2_SECRET_TOO_SHORT = -10, + ARGON2_SECRET_TOO_LONG = -11, + + ARGON2_TIME_TOO_SMALL = -12, + ARGON2_TIME_TOO_LARGE = -13, + + ARGON2_MEMORY_TOO_LITTLE = -14, + ARGON2_MEMORY_TOO_MUCH = -15, + + ARGON2_LANES_TOO_FEW = -16, + ARGON2_LANES_TOO_MANY = -17, + + ARGON2_PWD_PTR_MISMATCH = -18, /* NULL ptr with non-zero length */ + ARGON2_SALT_PTR_MISMATCH = -19, /* NULL ptr with non-zero length */ + ARGON2_SECRET_PTR_MISMATCH = -20, /* NULL ptr with non-zero length */ + ARGON2_AD_PTR_MISMATCH = -21, /* NULL ptr with non-zero length */ + + ARGON2_MEMORY_ALLOCATION_ERROR = -22, + + ARGON2_FREE_MEMORY_CBK_NULL = -23, + ARGON2_ALLOCATE_MEMORY_CBK_NULL = -24, + + ARGON2_INCORRECT_PARAMETER = -25, + ARGON2_INCORRECT_TYPE = -26, + + ARGON2_OUT_PTR_MISMATCH = -27, + + ARGON2_THREADS_TOO_FEW = -28, + ARGON2_THREADS_TOO_MANY = -29, + + ARGON2_MISSING_ARGS = -30, + + ARGON2_ENCODING_FAIL = -31, + + ARGON2_DECODING_FAIL = -32, + + ARGON2_THREAD_FAIL = -33, + + ARGON2_DECODING_LENGTH_FAIL = -34, + + ARGON2_VERIFY_MISMATCH = -35 +} argon2_error_codes; + +/* Memory allocator types --- for external allocation */ +typedef int (*allocate_fptr)(uint8_t **memory, size_t bytes_to_allocate); +typedef void (*deallocate_fptr)(uint8_t *memory, size_t bytes_to_allocate); + +/* Argon2 external data structures */ + +/* + ***** + * Context: structure to hold Argon2 inputs: + * output array and its length, + * password and its length, + * salt and its length, + * secret and its length, + * associated data and its length, + * number of passes, amount of used memory (in KBytes, can be rounded up a bit) + * number of parallel threads that will be run. + * All the parameters above affect the output hash value. + * Additionally, two function pointers can be provided to allocate and + * deallocate the memory (if NULL, memory will be allocated internally). + * Also, three flags indicate whether to erase password, secret as soon as they + * are pre-hashed (and thus not needed anymore), and the entire memory + ***** + * Simplest situation: you have output array out[8], password is stored in + * pwd[32], salt is stored in salt[16], you do not have keys nor associated + * data. You need to spend 1 GB of RAM and you run 5 passes of Argon2d with + * 4 parallel lanes. + * You want to erase the password, but you're OK with last pass not being + * erased. You want to use the default memory allocator. + * Then you initialize: + Argon2_Context(out,8,pwd,32,salt,16,NULL,0,NULL,0,5,1<<20,4,4,NULL,NULL,true,false,false,false) + */ +typedef struct Argon2_Context { + uint8_t *out; /* output array */ + uint32_t outlen; /* digest length */ + + uint8_t *pwd; /* password array */ + uint32_t pwdlen; /* password length */ + + uint8_t *salt; /* salt array */ + uint32_t saltlen; /* salt length */ + + uint8_t *secret; /* key array */ + uint32_t secretlen; /* key length */ + + uint8_t *ad; /* associated data array */ + uint32_t adlen; /* associated data length */ + + uint32_t t_cost; /* number of passes */ + uint32_t m_cost; /* amount of memory requested (KB) */ + uint32_t lanes; /* number of lanes */ + uint32_t threads; /* maximum number of threads */ + + uint32_t version; /* version number */ + + allocate_fptr allocate_cbk; /* pointer to memory allocator */ + deallocate_fptr free_cbk; /* pointer to memory deallocator */ + + uint32_t flags; /* array of bool options */ +} argon2_context; + +/* Argon2 primitive type */ +typedef enum Argon2_type { + Argon2_d = 0, + Argon2_i = 1, + Argon2_id = 2 +} argon2_type; + +/* Version of the algorithm */ +typedef enum Argon2_version { + ARGON2_VERSION_10 = 0x10, + ARGON2_VERSION_13 = 0x13, + ARGON2_VERSION_NUMBER = ARGON2_VERSION_13 +} argon2_version; + +/* + * Function that gives the string representation of an argon2_type. + * @param type The argon2_type that we want the string for + * @param uppercase Whether the string should have the first letter uppercase + * @return NULL if invalid type, otherwise the string representation. + */ +ARGON2_PUBLIC const char *argon2_type2string(argon2_type type, int uppercase); + +/* + * Function that performs memory-hard hashing with certain degree of parallelism + * @param context Pointer to the Argon2 internal structure + * @return Error code if smth is wrong, ARGON2_OK otherwise + */ +ARGON2_PUBLIC int argon2_ctx(argon2_context *context, argon2_type type); + +/** + * Hashes a password with Argon2i, producing an encoded hash + * @param t_cost Number of iterations + * @param m_cost Sets memory usage to m_cost kibibytes + * @param parallelism Number of threads and compute lanes + * @param pwd Pointer to password + * @param pwdlen Password size in bytes + * @param salt Pointer to salt + * @param saltlen Salt size in bytes + * @param hashlen Desired length of the hash in bytes + * @param encoded Buffer where to write the encoded hash + * @param encodedlen Size of the buffer (thus max size of the encoded hash) + * @pre Different parallelism levels will give different results + * @pre Returns ARGON2_OK if successful + */ +ARGON2_PUBLIC int argon2i_hash_encoded(const uint32_t t_cost, + const uint32_t m_cost, + const uint32_t parallelism, + const void *pwd, const size_t pwdlen, + const void *salt, const size_t saltlen, + const size_t hashlen, char *encoded, + const size_t encodedlen); + +/** + * Hashes a password with Argon2i, producing a raw hash at @hash + * @param t_cost Number of iterations + * @param m_cost Sets memory usage to m_cost kibibytes + * @param parallelism Number of threads and compute lanes + * @param pwd Pointer to password + * @param pwdlen Password size in bytes + * @param salt Pointer to salt + * @param saltlen Salt size in bytes + * @param hash Buffer where to write the raw hash - updated by the function + * @param hashlen Desired length of the hash in bytes + * @pre Different parallelism levels will give different results + * @pre Returns ARGON2_OK if successful + */ +ARGON2_PUBLIC int argon2i_hash_raw(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, + const size_t hashlen); + +ARGON2_PUBLIC int argon2d_hash_encoded(const uint32_t t_cost, + const uint32_t m_cost, + const uint32_t parallelism, + const void *pwd, const size_t pwdlen, + const void *salt, const size_t saltlen, + const size_t hashlen, char *encoded, + const size_t encodedlen); + +ARGON2_PUBLIC int argon2d_hash_raw(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, + const size_t hashlen); + +ARGON2_PUBLIC int argon2id_hash_encoded(const uint32_t t_cost, + const uint32_t m_cost, + const uint32_t parallelism, + const void *pwd, const size_t pwdlen, + const void *salt, const size_t saltlen, + const size_t hashlen, char *encoded, + const size_t encodedlen); + +ARGON2_PUBLIC int argon2id_hash_raw(const uint32_t t_cost, + const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, + const size_t hashlen); + +/* generic function underlying the above ones */ +ARGON2_PUBLIC int argon2_hash(const uint32_t t_cost, const uint32_t m_cost, + const uint32_t parallelism, const void *pwd, + const size_t pwdlen, const void *salt, + const size_t saltlen, void *hash, + const size_t hashlen, char *encoded, + const size_t encodedlen, argon2_type type, + const uint32_t version); + +/** + * Verifies a password against an encoded string + * Encoded string is restricted as in validate_inputs() + * @param encoded String encoding parameters, salt, hash + * @param pwd Pointer to password + * @pre Returns ARGON2_OK if successful + */ +ARGON2_PUBLIC int argon2i_verify(const char *encoded, const void *pwd, + const size_t pwdlen); + +ARGON2_PUBLIC int argon2d_verify(const char *encoded, const void *pwd, + const size_t pwdlen); + +ARGON2_PUBLIC int argon2id_verify(const char *encoded, const void *pwd, + const size_t pwdlen); + +/* generic function underlying the above ones */ +ARGON2_PUBLIC int argon2_verify(const char *encoded, const void *pwd, + const size_t pwdlen, argon2_type type); + +/** + * Argon2d: Version of Argon2 that picks memory blocks depending + * on the password and salt. Only for side-channel-free + * environment!! + ***** + * @param context Pointer to current Argon2 context + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2d_ctx(argon2_context *context); + +/** + * Argon2i: Version of Argon2 that picks memory blocks + * independent on the password and salt. Good for side-channels, + * but worse w.r.t. tradeoff attacks if only one pass is used. + ***** + * @param context Pointer to current Argon2 context + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2i_ctx(argon2_context *context); + +/** + * Argon2id: Version of Argon2 where the first half-pass over memory is + * password-independent, the rest are password-dependent (on the password and + * salt). OK against side channels (they reduce to 1/2-pass Argon2i), and + * better with w.r.t. tradeoff attacks (similar to Argon2d). + ***** + * @param context Pointer to current Argon2 context + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2id_ctx(argon2_context *context); + +/** + * Verify if a given password is correct for Argon2d hashing + * @param context Pointer to current Argon2 context + * @param hash The password hash to verify. The length of the hash is + * specified by the context outlen member + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2d_verify_ctx(argon2_context *context, const char *hash); + +/** + * Verify if a given password is correct for Argon2i hashing + * @param context Pointer to current Argon2 context + * @param hash The password hash to verify. The length of the hash is + * specified by the context outlen member + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2i_verify_ctx(argon2_context *context, const char *hash); + +/** + * Verify if a given password is correct for Argon2id hashing + * @param context Pointer to current Argon2 context + * @param hash The password hash to verify. The length of the hash is + * specified by the context outlen member + * @return Zero if successful, a non zero error code otherwise + */ +ARGON2_PUBLIC int argon2id_verify_ctx(argon2_context *context, + const char *hash); + +/* generic function underlying the above ones */ +ARGON2_PUBLIC int argon2_verify_ctx(argon2_context *context, const char *hash, + argon2_type type); + +/** + * Get the associated error message for given error code + * @return The error message associated with the given error code + */ +ARGON2_PUBLIC const char *argon2_error_message(int error_code); + +/** + * Returns the encoded hash length for the given input parameters + * @param t_cost Number of iterations + * @param m_cost Memory usage in kibibytes + * @param parallelism Number of threads; used to compute lanes + * @param saltlen Salt size in bytes + * @param hashlen Hash size in bytes + * @param type The argon2_type that we want the encoded length for + * @return The encoded hash length in bytes + */ +ARGON2_PUBLIC size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, + uint32_t parallelism, uint32_t saltlen, + uint32_t hashlen, argon2_type type); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/lib/argon2/blake2/blake2-impl.h b/lib/argon2/blake2/blake2-impl.h new file mode 100644 index 00000000000..86d0d5cfa9b --- /dev/null +++ b/lib/argon2/blake2/blake2-impl.h @@ -0,0 +1,156 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef PORTABLE_BLAKE2_IMPL_H +#define PORTABLE_BLAKE2_IMPL_H + +#include <stdint.h> +#include <string.h> + +#ifdef _WIN32 +#define BLAKE2_INLINE __inline +#elif defined(__GNUC__) || defined(__clang__) +#define BLAKE2_INLINE __inline__ +#else +#define BLAKE2_INLINE +#endif + +/* Argon2 Team - Begin Code */ +/* + Not an exhaustive list, but should cover the majority of modern platforms + Additionally, the code will always be correct---this is only a performance + tweak. +*/ +#if (defined(__BYTE_ORDER__) && \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \ + defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \ + defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) || \ + defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || \ + defined(_M_ARM) +#define NATIVE_LITTLE_ENDIAN +#endif +/* Argon2 Team - End Code */ + +static BLAKE2_INLINE uint32_t load32(const void *src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *)src; + uint32_t w = *p++; + w |= (uint32_t)(*p++) << 8; + w |= (uint32_t)(*p++) << 16; + w |= (uint32_t)(*p++) << 24; + return w; +#endif +} + +static BLAKE2_INLINE uint64_t load64(const void *src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *)src; + uint64_t w = *p++; + w |= (uint64_t)(*p++) << 8; + w |= (uint64_t)(*p++) << 16; + w |= (uint64_t)(*p++) << 24; + w |= (uint64_t)(*p++) << 32; + w |= (uint64_t)(*p++) << 40; + w |= (uint64_t)(*p++) << 48; + w |= (uint64_t)(*p++) << 56; + return w; +#endif +} + +static BLAKE2_INLINE void store32(void *dst, uint32_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +static BLAKE2_INLINE void store64(void *dst, uint64_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +static BLAKE2_INLINE uint64_t load48(const void *src) { + const uint8_t *p = (const uint8_t *)src; + uint64_t w = *p++; + w |= (uint64_t)(*p++) << 8; + w |= (uint64_t)(*p++) << 16; + w |= (uint64_t)(*p++) << 24; + w |= (uint64_t)(*p++) << 32; + w |= (uint64_t)(*p++) << 40; + return w; +} + +static BLAKE2_INLINE void store48(void *dst, uint64_t w) { + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +} + +static BLAKE2_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) { + return (w >> c) | (w << (32 - c)); +} + +static BLAKE2_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) { + return (w >> c) | (w << (64 - c)); +} + +void clear_internal_memory(void *v, size_t n); + +#endif diff --git a/lib/argon2/blake2/blake2.h b/lib/argon2/blake2/blake2.h new file mode 100644 index 00000000000..501c6a3186d --- /dev/null +++ b/lib/argon2/blake2/blake2.h @@ -0,0 +1,89 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef PORTABLE_BLAKE2_H +#define PORTABLE_BLAKE2_H + +#include <argon2.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +enum blake2b_constant { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 +}; + +#pragma pack(push, 1) +typedef struct __blake2b_param { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint64_t node_offset; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ +} blake2b_param; +#pragma pack(pop) + +typedef struct __blake2b_state { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + unsigned buflen; + unsigned outlen; + uint8_t last_node; +} blake2b_state; + +/* Ensure param structs have not been wrongly padded */ +/* Poor man's static_assert */ +enum { + blake2_size_check_0 = 1 / !!(CHAR_BIT == 8), + blake2_size_check_2 = + 1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT) +}; + +/* Streaming API */ +ARGON2_LOCAL int blake2b_init(blake2b_state *S, size_t outlen); +ARGON2_LOCAL int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, + size_t keylen); +ARGON2_LOCAL int blake2b_init_param(blake2b_state *S, const blake2b_param *P); +ARGON2_LOCAL int blake2b_update(blake2b_state *S, const void *in, size_t inlen); +ARGON2_LOCAL int blake2b_final(blake2b_state *S, void *out, size_t outlen); + +/* Simple API */ +ARGON2_LOCAL int blake2b(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen); + +/* Argon2 Team - Begin Code */ +ARGON2_LOCAL int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen); +/* Argon2 Team - End Code */ + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/lib/argon2/blake2/blake2b.c b/lib/argon2/blake2/blake2b.c new file mode 100644 index 00000000000..3b519ddb4d0 --- /dev/null +++ b/lib/argon2/blake2/blake2b.c @@ -0,0 +1,390 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include "blake2.h" +#include "blake2-impl.h" + +static const uint64_t blake2b_IV[8] = { + UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b), + UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1), + UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f), + UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179)}; + +static const unsigned int blake2b_sigma[12][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, +}; + +static BLAKE2_INLINE void blake2b_set_lastnode(blake2b_state *S) { + S->f[1] = (uint64_t)-1; +} + +static BLAKE2_INLINE void blake2b_set_lastblock(blake2b_state *S) { + if (S->last_node) { + blake2b_set_lastnode(S); + } + S->f[0] = (uint64_t)-1; +} + +static BLAKE2_INLINE void blake2b_increment_counter(blake2b_state *S, + uint64_t inc) { + S->t[0] += inc; + S->t[1] += (S->t[0] < inc); +} + +static BLAKE2_INLINE void blake2b_invalidate_state(blake2b_state *S) { + clear_internal_memory(S, sizeof(*S)); /* wipe */ + blake2b_set_lastblock(S); /* invalidate for further use */ +} + +static BLAKE2_INLINE void blake2b_init0(blake2b_state *S) { + memset(S, 0, sizeof(*S)); + memcpy(S->h, blake2b_IV, sizeof(S->h)); +} + +int blake2b_init_param(blake2b_state *S, const blake2b_param *P) { + const unsigned char *p = (const unsigned char *)P; + unsigned int i; + + if (NULL == P || NULL == S) { + return -1; + } + + blake2b_init0(S); + /* IV XOR Parameter Block */ + for (i = 0; i < 8; ++i) { + S->h[i] ^= load64(&p[i * sizeof(S->h[i])]); + } + S->outlen = P->digest_length; + return 0; +} + +/* Sequential blake2b initialization */ +int blake2b_init(blake2b_state *S, size_t outlen) { + blake2b_param P; + + if (S == NULL) { + return -1; + } + + if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + /* Setup Parameter Block for unkeyed BLAKE2 */ + P.digest_length = (uint8_t)outlen; + P.key_length = 0; + P.fanout = 1; + P.depth = 1; + P.leaf_length = 0; + P.node_offset = 0; + P.node_depth = 0; + P.inner_length = 0; + memset(P.reserved, 0, sizeof(P.reserved)); + memset(P.salt, 0, sizeof(P.salt)); + memset(P.personal, 0, sizeof(P.personal)); + + return blake2b_init_param(S, &P); +} + +int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, + size_t keylen) { + blake2b_param P; + + if (S == NULL) { + return -1; + } + + if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + /* Setup Parameter Block for keyed BLAKE2 */ + P.digest_length = (uint8_t)outlen; + P.key_length = (uint8_t)keylen; + P.fanout = 1; + P.depth = 1; + P.leaf_length = 0; + P.node_offset = 0; + P.node_depth = 0; + P.inner_length = 0; + memset(P.reserved, 0, sizeof(P.reserved)); + memset(P.salt, 0, sizeof(P.salt)); + memset(P.personal, 0, sizeof(P.personal)); + + if (blake2b_init_param(S, &P) < 0) { + blake2b_invalidate_state(S); + return -1; + } + + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S, block, BLAKE2B_BLOCKBYTES); + /* Burn the key from stack */ + clear_internal_memory(block, BLAKE2B_BLOCKBYTES); + } + return 0; +} + +static void blake2b_compress(blake2b_state *S, const uint8_t *block) { + uint64_t m[16]; + uint64_t v[16]; + unsigned int i, r; + + for (i = 0; i < 16; ++i) { + m[i] = load64(block + i * sizeof(m[i])); + } + + for (i = 0; i < 8; ++i) { + v[i] = S->h[i]; + } + + v[8] = blake2b_IV[0]; + v[9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7] ^ S->f[1]; + +#define G(r, i, a, b, c, d) \ + do { \ + a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while ((void)0, 0) + +#define ROUND(r) \ + do { \ + G(r, 0, v[0], v[4], v[8], v[12]); \ + G(r, 1, v[1], v[5], v[9], v[13]); \ + G(r, 2, v[2], v[6], v[10], v[14]); \ + G(r, 3, v[3], v[7], v[11], v[15]); \ + G(r, 4, v[0], v[5], v[10], v[15]); \ + G(r, 5, v[1], v[6], v[11], v[12]); \ + G(r, 6, v[2], v[7], v[8], v[13]); \ + G(r, 7, v[3], v[4], v[9], v[14]); \ + } while ((void)0, 0) + + for (r = 0; r < 12; ++r) { + ROUND(r); + } + + for (i = 0; i < 8; ++i) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } + +#undef G +#undef ROUND +} + +int blake2b_update(blake2b_state *S, const void *in, size_t inlen) { + const uint8_t *pin = (const uint8_t *)in; + + if (inlen == 0) { + return 0; + } + + /* Sanity check */ + if (S == NULL || in == NULL) { + return -1; + } + + /* Is this a reused state? */ + if (S->f[0] != 0) { + return -1; + } + + if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) { + /* Complete current block */ + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + memcpy(&S->buf[left], pin, fill); + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, S->buf); + S->buflen = 0; + inlen -= fill; + pin += fill; + /* Avoid buffer copies when possible */ + while (inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, pin); + inlen -= BLAKE2B_BLOCKBYTES; + pin += BLAKE2B_BLOCKBYTES; + } + } + memcpy(&S->buf[S->buflen], pin, inlen); + S->buflen += (unsigned int)inlen; + return 0; +} + +int blake2b_final(blake2b_state *S, void *out, size_t outlen) { + uint8_t buffer[BLAKE2B_OUTBYTES] = {0}; + unsigned int i; + + /* Sanity checks */ + if (S == NULL || out == NULL || outlen < S->outlen) { + return -1; + } + + /* Is this a reused state? */ + if (S->f[0] != 0) { + return -1; + } + + blake2b_increment_counter(S, S->buflen); + blake2b_set_lastblock(S); + memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */ + blake2b_compress(S, S->buf); + + for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */ + store64(buffer + sizeof(S->h[i]) * i, S->h[i]); + } + + memcpy(out, buffer, S->outlen); + clear_internal_memory(buffer, sizeof(buffer)); + clear_internal_memory(S->buf, sizeof(S->buf)); + clear_internal_memory(S->h, sizeof(S->h)); + return 0; +} + +int blake2b(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen) { + blake2b_state S; + int ret = -1; + + /* Verify parameters */ + if (NULL == in && inlen > 0) { + goto fail; + } + + if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) { + goto fail; + } + + if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) { + goto fail; + } + + if (keylen > 0) { + if (blake2b_init_key(&S, outlen, key, keylen) < 0) { + goto fail; + } + } else { + if (blake2b_init(&S, outlen) < 0) { + goto fail; + } + } + + if (blake2b_update(&S, in, inlen) < 0) { + goto fail; + } + ret = blake2b_final(&S, out, outlen); + +fail: + clear_internal_memory(&S, sizeof(S)); + return ret; +} + +/* Argon2 Team - Begin Code */ +int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) { + uint8_t *out = (uint8_t *)pout; + blake2b_state blake_state; + uint8_t outlen_bytes[sizeof(uint32_t)] = {0}; + int ret = -1; + + if (outlen > UINT32_MAX) { + goto fail; + } + + /* Ensure little-endian byte order! */ + store32(outlen_bytes, (uint32_t)outlen); + +#define TRY(statement) \ + do { \ + ret = statement; \ + if (ret < 0) { \ + goto fail; \ + } \ + } while ((void)0, 0) + + if (outlen <= BLAKE2B_OUTBYTES) { + TRY(blake2b_init(&blake_state, outlen)); + TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes))); + TRY(blake2b_update(&blake_state, in, inlen)); + TRY(blake2b_final(&blake_state, out, outlen)); + } else { + uint32_t toproduce; + uint8_t out_buffer[BLAKE2B_OUTBYTES]; + uint8_t in_buffer[BLAKE2B_OUTBYTES]; + TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES)); + TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes))); + TRY(blake2b_update(&blake_state, in, inlen)); + TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES)); + memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2); + out += BLAKE2B_OUTBYTES / 2; + toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2; + + while (toproduce > BLAKE2B_OUTBYTES) { + memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES); + TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer, + BLAKE2B_OUTBYTES, NULL, 0)); + memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2); + out += BLAKE2B_OUTBYTES / 2; + toproduce -= BLAKE2B_OUTBYTES / 2; + } + + memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES); + TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL, + 0)); + memcpy(out, out_buffer, toproduce); + } +fail: + clear_internal_memory(&blake_state, sizeof(blake_state)); + return ret; +#undef TRY +} +/* Argon2 Team - End Code */ diff --git a/lib/argon2/blake2/blamka-round-ref.h b/lib/argon2/blake2/blamka-round-ref.h new file mode 100644 index 00000000000..16cfc1c74af --- /dev/null +++ b/lib/argon2/blake2/blamka-round-ref.h @@ -0,0 +1,56 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef BLAKE_ROUND_MKA_H +#define BLAKE_ROUND_MKA_H + +#include "blake2.h" +#include "blake2-impl.h" + +/* designed by the Lyra PHC team */ +static BLAKE2_INLINE uint64_t fBlaMka(uint64_t x, uint64_t y) { + const uint64_t m = UINT64_C(0xFFFFFFFF); + const uint64_t xy = (x & m) * (y & m); + return x + y + 2 * xy; +} + +#define G(a, b, c, d) \ + do { \ + a = fBlaMka(a, b); \ + d = rotr64(d ^ a, 32); \ + c = fBlaMka(c, d); \ + b = rotr64(b ^ c, 24); \ + a = fBlaMka(a, b); \ + d = rotr64(d ^ a, 16); \ + c = fBlaMka(c, d); \ + b = rotr64(b ^ c, 63); \ + } while ((void)0, 0) + +#define BLAKE2_ROUND_NOMSG(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, \ + v12, v13, v14, v15) \ + do { \ + G(v0, v4, v8, v12); \ + G(v1, v5, v9, v13); \ + G(v2, v6, v10, v14); \ + G(v3, v7, v11, v15); \ + G(v0, v5, v10, v15); \ + G(v1, v6, v11, v12); \ + G(v2, v7, v8, v13); \ + G(v3, v4, v9, v14); \ + } while ((void)0, 0) + +#endif diff --git a/lib/argon2/core.c b/lib/argon2/core.c new file mode 100644 index 00000000000..e697882eb96 --- /dev/null +++ b/lib/argon2/core.c @@ -0,0 +1,648 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +/*For memory wiping*/ +#ifdef _WIN32 +#include <windows.h> +#include <winbase.h> /* For SecureZeroMemory */ +#endif +#if defined __STDC_LIB_EXT1__ +#define __STDC_WANT_LIB_EXT1__ 1 +#endif +#define VC_GE_2005(version) (version >= 1400) + +/* for explicit_bzero() on glibc */ +#define _DEFAULT_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "core.h" +#include "thread.h" +#include "blake2/blake2.h" +#include "blake2/blake2-impl.h" + +#ifdef GENKAT +#include "genkat.h" +#endif + +#if defined(__clang__) +#if __has_attribute(optnone) +#define NOT_OPTIMIZED __attribute__((optnone)) +#endif +#elif defined(__GNUC__) +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if GCC_VERSION >= 40400 +#define NOT_OPTIMIZED __attribute__((optimize("O0"))) +#endif +#endif +#ifndef NOT_OPTIMIZED +#define NOT_OPTIMIZED +#endif + +/***************Instance and Position constructors**********/ +void init_block_value(block *b, uint8_t in) { memset(b->v, in, sizeof(b->v)); } + +void copy_block(block *dst, const block *src) { + memcpy(dst->v, src->v, sizeof(uint64_t) * ARGON2_QWORDS_IN_BLOCK); +} + +void xor_block(block *dst, const block *src) { + int i; + for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) { + dst->v[i] ^= src->v[i]; + } +} + +static void load_block(block *dst, const void *input) { + unsigned i; + for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) { + dst->v[i] = load64((const uint8_t *)input + i * sizeof(dst->v[i])); + } +} + +static void store_block(void *output, const block *src) { + unsigned i; + for (i = 0; i < ARGON2_QWORDS_IN_BLOCK; ++i) { + store64((uint8_t *)output + i * sizeof(src->v[i]), src->v[i]); + } +} + +/***************Memory functions*****************/ + +int allocate_memory(const argon2_context *context, uint8_t **memory, + size_t num, size_t size) { + size_t memory_size = num*size; + if (memory == NULL) { + return ARGON2_MEMORY_ALLOCATION_ERROR; + } + + /* 1. Check for multiplication overflow */ + if (size != 0 && memory_size / size != num) { + return ARGON2_MEMORY_ALLOCATION_ERROR; + } + + /* 2. Try to allocate with appropriate allocator */ + if (context->allocate_cbk) { + (context->allocate_cbk)(memory, memory_size); + } else { + *memory = malloc(memory_size); + } + + if (*memory == NULL) { + return ARGON2_MEMORY_ALLOCATION_ERROR; + } + + return ARGON2_OK; +} + +void free_memory(const argon2_context *context, uint8_t *memory, + size_t num, size_t size) { + size_t memory_size = num*size; + clear_internal_memory(memory, memory_size); + if (context->free_cbk) { + (context->free_cbk)(memory, memory_size); + } else { + free(memory); + } +} + +#if defined(__OpenBSD__) +#define HAVE_EXPLICIT_BZERO 1 +#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ) +#if __GLIBC_PREREQ(2,25) +#define HAVE_EXPLICIT_BZERO 1 +#endif +#endif + +void NOT_OPTIMIZED secure_wipe_memory(void *v, size_t n) { +#if defined(_MSC_VER) && VC_GE_2005(_MSC_VER) || defined(__MINGW32__) + SecureZeroMemory(v, n); +#elif defined memset_s + memset_s(v, n, 0, n); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero(v, n); +#else + static void *(*const volatile memset_sec)(void *, int, size_t) = &memset; + memset_sec(v, 0, n); +#endif +} + +/* Memory clear flag defaults to true. */ +int FLAG_clear_internal_memory = 1; +void clear_internal_memory(void *v, size_t n) { + if (FLAG_clear_internal_memory && v) { + secure_wipe_memory(v, n); + } +} + +void finalize(const argon2_context *context, argon2_instance_t *instance) { + if (context != NULL && instance != NULL) { + block blockhash; + uint32_t l; + + copy_block(&blockhash, instance->memory + instance->lane_length - 1); + + /* XOR the last blocks */ + for (l = 1; l < instance->lanes; ++l) { + uint32_t last_block_in_lane = + l * instance->lane_length + (instance->lane_length - 1); + xor_block(&blockhash, instance->memory + last_block_in_lane); + } + + /* Hash the result */ + { + uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE]; + store_block(blockhash_bytes, &blockhash); + blake2b_long(context->out, context->outlen, blockhash_bytes, + ARGON2_BLOCK_SIZE); + /* clear blockhash and blockhash_bytes */ + clear_internal_memory(blockhash.v, ARGON2_BLOCK_SIZE); + clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE); + } + +#ifdef GENKAT + print_tag(context->out, context->outlen); +#endif + + free_memory(context, (uint8_t *)instance->memory, + instance->memory_blocks, sizeof(block)); + } +} + +uint32_t index_alpha(const argon2_instance_t *instance, + const argon2_position_t *position, uint32_t pseudo_rand, + int same_lane) { + /* + * Pass 0: + * This lane : all already finished segments plus already constructed + * blocks in this segment + * Other lanes : all already finished segments + * Pass 1+: + * This lane : (SYNC_POINTS - 1) last segments plus already constructed + * blocks in this segment + * Other lanes : (SYNC_POINTS - 1) last segments + */ + uint32_t reference_area_size; + uint64_t relative_position; + uint32_t start_position, absolute_position; + + if (0 == position->pass) { + /* First pass */ + if (0 == position->slice) { + /* First slice */ + reference_area_size = + position->index - 1; /* all but the previous */ + } else { + if (same_lane) { + /* The same lane => add current segment */ + reference_area_size = + position->slice * instance->segment_length + + position->index - 1; + } else { + reference_area_size = + position->slice * instance->segment_length + + ((position->index == 0) ? (-1) : 0); + } + } + } else { + /* Second pass */ + if (same_lane) { + reference_area_size = instance->lane_length - + instance->segment_length + position->index - + 1; + } else { + reference_area_size = instance->lane_length - + instance->segment_length + + ((position->index == 0) ? (-1) : 0); + } + } + + /* 1.2.4. Mapping pseudo_rand to 0..<reference_area_size-1> and produce + * relative position */ + relative_position = pseudo_rand; + relative_position = relative_position * relative_position >> 32; + relative_position = reference_area_size - 1 - + (reference_area_size * relative_position >> 32); + + /* 1.2.5 Computing starting position */ + start_position = 0; + + if (0 != position->pass) { + start_position = (position->slice == ARGON2_SYNC_POINTS - 1) + ? 0 + : (position->slice + 1) * instance->segment_length; + } + + /* 1.2.6. Computing absolute position */ + absolute_position = (start_position + relative_position) % + instance->lane_length; /* absolute position */ + return absolute_position; +} + +/* Single-threaded version for p=1 case */ +static int fill_memory_blocks_st(argon2_instance_t *instance) { + uint32_t r, s, l; + + for (r = 0; r < instance->passes; ++r) { + for (s = 0; s < ARGON2_SYNC_POINTS; ++s) { + for (l = 0; l < instance->lanes; ++l) { + argon2_position_t position = {r, l, (uint8_t)s, 0}; + fill_segment(instance, position); + } + } +#ifdef GENKAT + internal_kat(instance, r); /* Print all memory blocks */ +#endif + } + return ARGON2_OK; +} + +#if !defined(ARGON2_NO_THREADS) + +#ifdef _WIN32 +static unsigned __stdcall fill_segment_thr(void *thread_data) +#else +static void *fill_segment_thr(void *thread_data) +#endif +{ + argon2_thread_data *my_data = thread_data; + fill_segment(my_data->instance_ptr, my_data->pos); + argon2_thread_exit(); + return 0; +} + +/* Multi-threaded version for p > 1 case */ +static int fill_memory_blocks_mt(argon2_instance_t *instance) { + uint32_t r, s; + argon2_thread_handle_t *thread = NULL; + argon2_thread_data *thr_data = NULL; + int rc = ARGON2_OK; + + /* 1. Allocating space for threads */ + thread = calloc(instance->lanes, sizeof(argon2_thread_handle_t)); + if (thread == NULL) { + rc = ARGON2_MEMORY_ALLOCATION_ERROR; + goto fail; + } + + thr_data = calloc(instance->lanes, sizeof(argon2_thread_data)); + if (thr_data == NULL) { + rc = ARGON2_MEMORY_ALLOCATION_ERROR; + goto fail; + } + + for (r = 0; r < instance->passes; ++r) { + for (s = 0; s < ARGON2_SYNC_POINTS; ++s) { + uint32_t l, ll; + + /* 2. Calling threads */ + for (l = 0; l < instance->lanes; ++l) { + argon2_position_t position; + + /* 2.1 Join a thread if limit is exceeded */ + if (l >= instance->threads) { + if (argon2_thread_join(thread[l - instance->threads])) { + rc = ARGON2_THREAD_FAIL; + goto fail; + } + } + + /* 2.2 Create thread */ + position.pass = r; + position.lane = l; + position.slice = (uint8_t)s; + position.index = 0; + thr_data[l].instance_ptr = + instance; /* preparing the thread input */ + memcpy(&(thr_data[l].pos), &position, + sizeof(argon2_position_t)); + if (argon2_thread_create(&thread[l], &fill_segment_thr, + (void *)&thr_data[l])) { + /* Wait for already running threads */ + for (ll = 0; ll < l; ++ll) + argon2_thread_join(thread[ll]); + rc = ARGON2_THREAD_FAIL; + goto fail; + } + + /* fill_segment(instance, position); */ + /*Non-thread equivalent of the lines above */ + } + + /* 3. Joining remaining threads */ + for (l = instance->lanes - instance->threads; l < instance->lanes; + ++l) { + if (argon2_thread_join(thread[l])) { + rc = ARGON2_THREAD_FAIL; + goto fail; + } + } + } + +#ifdef GENKAT + internal_kat(instance, r); /* Print all memory blocks */ +#endif + } + +fail: + if (thread != NULL) { + free(thread); + } + if (thr_data != NULL) { + free(thr_data); + } + return rc; +} + +#endif /* ARGON2_NO_THREADS */ + +int fill_memory_blocks(argon2_instance_t *instance) { + if (instance == NULL || instance->lanes == 0) { + return ARGON2_INCORRECT_PARAMETER; + } +#if defined(ARGON2_NO_THREADS) + return fill_memory_blocks_st(instance); +#else + return instance->threads == 1 ? + fill_memory_blocks_st(instance) : fill_memory_blocks_mt(instance); +#endif +} + +int validate_inputs(const argon2_context *context) { + if (NULL == context) { + return ARGON2_INCORRECT_PARAMETER; + } + + if (NULL == context->out) { + return ARGON2_OUTPUT_PTR_NULL; + } + + /* Validate output length */ + if (ARGON2_MIN_OUTLEN > context->outlen) { + return ARGON2_OUTPUT_TOO_SHORT; + } + + if (ARGON2_MAX_OUTLEN < context->outlen) { + return ARGON2_OUTPUT_TOO_LONG; + } + + /* Validate password (required param) */ + if (NULL == context->pwd) { + if (0 != context->pwdlen) { + return ARGON2_PWD_PTR_MISMATCH; + } + } + + if (ARGON2_MIN_PWD_LENGTH > context->pwdlen) { + return ARGON2_PWD_TOO_SHORT; + } + + if (ARGON2_MAX_PWD_LENGTH < context->pwdlen) { + return ARGON2_PWD_TOO_LONG; + } + + /* Validate salt (required param) */ + if (NULL == context->salt) { + if (0 != context->saltlen) { + return ARGON2_SALT_PTR_MISMATCH; + } + } + + if (ARGON2_MIN_SALT_LENGTH > context->saltlen) { + return ARGON2_SALT_TOO_SHORT; + } + + if (ARGON2_MAX_SALT_LENGTH < context->saltlen) { + return ARGON2_SALT_TOO_LONG; + } + + /* Validate secret (optional param) */ + if (NULL == context->secret) { + if (0 != context->secretlen) { + return ARGON2_SECRET_PTR_MISMATCH; + } + } else { + if (ARGON2_MIN_SECRET > context->secretlen) { + return ARGON2_SECRET_TOO_SHORT; + } + if (ARGON2_MAX_SECRET < context->secretlen) { + return ARGON2_SECRET_TOO_LONG; + } + } + + /* Validate associated data (optional param) */ + if (NULL == context->ad) { + if (0 != context->adlen) { + return ARGON2_AD_PTR_MISMATCH; + } + } else { + if (ARGON2_MIN_AD_LENGTH > context->adlen) { + return ARGON2_AD_TOO_SHORT; + } + if (ARGON2_MAX_AD_LENGTH < context->adlen) { + return ARGON2_AD_TOO_LONG; + } + } + + /* Validate memory cost */ + if (ARGON2_MIN_MEMORY > context->m_cost) { + return ARGON2_MEMORY_TOO_LITTLE; + } + + if (ARGON2_MAX_MEMORY < context->m_cost) { + return ARGON2_MEMORY_TOO_MUCH; + } + + if (context->m_cost < 8 * context->lanes) { + return ARGON2_MEMORY_TOO_LITTLE; + } + + /* Validate time cost */ + if (ARGON2_MIN_TIME > context->t_cost) { + return ARGON2_TIME_TOO_SMALL; + } + + if (ARGON2_MAX_TIME < context->t_cost) { + return ARGON2_TIME_TOO_LARGE; + } + + /* Validate lanes */ + if (ARGON2_MIN_LANES > context->lanes) { + return ARGON2_LANES_TOO_FEW; + } + + if (ARGON2_MAX_LANES < context->lanes) { + return ARGON2_LANES_TOO_MANY; + } + + /* Validate threads */ + if (ARGON2_MIN_THREADS > context->threads) { + return ARGON2_THREADS_TOO_FEW; + } + + if (ARGON2_MAX_THREADS < context->threads) { + return ARGON2_THREADS_TOO_MANY; + } + + if (NULL != context->allocate_cbk && NULL == context->free_cbk) { + return ARGON2_FREE_MEMORY_CBK_NULL; + } + + if (NULL == context->allocate_cbk && NULL != context->free_cbk) { + return ARGON2_ALLOCATE_MEMORY_CBK_NULL; + } + + return ARGON2_OK; +} + +void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance) { + uint32_t l; + /* Make the first and second block in each lane as G(H0||0||i) or + G(H0||1||i) */ + uint8_t blockhash_bytes[ARGON2_BLOCK_SIZE]; + for (l = 0; l < instance->lanes; ++l) { + + store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 0); + store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH + 4, l); + blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash, + ARGON2_PREHASH_SEED_LENGTH); + load_block(&instance->memory[l * instance->lane_length + 0], + blockhash_bytes); + + store32(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, 1); + blake2b_long(blockhash_bytes, ARGON2_BLOCK_SIZE, blockhash, + ARGON2_PREHASH_SEED_LENGTH); + load_block(&instance->memory[l * instance->lane_length + 1], + blockhash_bytes); + } + clear_internal_memory(blockhash_bytes, ARGON2_BLOCK_SIZE); +} + +void initial_hash(uint8_t *blockhash, argon2_context *context, + argon2_type type) { + blake2b_state BlakeHash; + uint8_t value[sizeof(uint32_t)]; + + if (NULL == context || NULL == blockhash) { + return; + } + + blake2b_init(&BlakeHash, ARGON2_PREHASH_DIGEST_LENGTH); + + store32(&value, context->lanes); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, context->outlen); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, context->m_cost); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, context->t_cost); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, context->version); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, (uint32_t)type); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + store32(&value, context->pwdlen); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + if (context->pwd != NULL) { + blake2b_update(&BlakeHash, (const uint8_t *)context->pwd, + context->pwdlen); + + if (context->flags & ARGON2_FLAG_CLEAR_PASSWORD) { + secure_wipe_memory(context->pwd, context->pwdlen); + context->pwdlen = 0; + } + } + + store32(&value, context->saltlen); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + if (context->salt != NULL) { + blake2b_update(&BlakeHash, (const uint8_t *)context->salt, + context->saltlen); + } + + store32(&value, context->secretlen); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + if (context->secret != NULL) { + blake2b_update(&BlakeHash, (const uint8_t *)context->secret, + context->secretlen); + + if (context->flags & ARGON2_FLAG_CLEAR_SECRET) { + secure_wipe_memory(context->secret, context->secretlen); + context->secretlen = 0; + } + } + + store32(&value, context->adlen); + blake2b_update(&BlakeHash, (const uint8_t *)&value, sizeof(value)); + + if (context->ad != NULL) { + blake2b_update(&BlakeHash, (const uint8_t *)context->ad, + context->adlen); + } + + blake2b_final(&BlakeHash, blockhash, ARGON2_PREHASH_DIGEST_LENGTH); +} + +int initialize(argon2_instance_t *instance, argon2_context *context) { + uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; + int result = ARGON2_OK; + + if (instance == NULL || context == NULL) + return ARGON2_INCORRECT_PARAMETER; + instance->context_ptr = context; + + /* 1. Memory allocation */ + result = allocate_memory(context, (uint8_t **)&(instance->memory), + instance->memory_blocks, sizeof(block)); + if (result != ARGON2_OK) { + return result; + } + + /* 2. Initial hashing */ + /* H_0 + 8 extra bytes to produce the first blocks */ + /* uint8_t blockhash[ARGON2_PREHASH_SEED_LENGTH]; */ + /* Hashing all inputs */ + initial_hash(blockhash, context, instance->type); + /* Zeroing 8 extra bytes */ + clear_internal_memory(blockhash + ARGON2_PREHASH_DIGEST_LENGTH, + ARGON2_PREHASH_SEED_LENGTH - + ARGON2_PREHASH_DIGEST_LENGTH); + +#ifdef GENKAT + initial_kat(blockhash, context, instance->type); +#endif + + /* 3. Creating first blocks, we always have at least two blocks in a slice + */ + fill_first_blocks(blockhash, instance); + /* Clearing the hash */ + clear_internal_memory(blockhash, ARGON2_PREHASH_SEED_LENGTH); + + return ARGON2_OK; +} diff --git a/lib/argon2/core.h b/lib/argon2/core.h new file mode 100644 index 00000000000..59e25646cbc --- /dev/null +++ b/lib/argon2/core.h @@ -0,0 +1,228 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#ifndef ARGON2_CORE_H +#define ARGON2_CORE_H + +#include "argon2.h" + +#define CONST_CAST(x) (x)(uintptr_t) + +/**********************Argon2 internal constants*******************************/ + +enum argon2_core_constants { + /* Memory block size in bytes */ + ARGON2_BLOCK_SIZE = 1024, + ARGON2_QWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 8, + ARGON2_OWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 16, + ARGON2_HWORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 32, + ARGON2_512BIT_WORDS_IN_BLOCK = ARGON2_BLOCK_SIZE / 64, + + /* Number of pseudo-random values generated by one call to Blake in Argon2i + to + generate reference block positions */ + ARGON2_ADDRESSES_IN_BLOCK = 128, + + /* Pre-hashing digest length and its extension*/ + ARGON2_PREHASH_DIGEST_LENGTH = 64, + ARGON2_PREHASH_SEED_LENGTH = 72 +}; + +/*************************Argon2 internal data types***********************/ + +/* + * Structure for the (1KB) memory block implemented as 128 64-bit words. + * Memory blocks can be copied, XORed. Internal words can be accessed by [] (no + * bounds checking). + */ +typedef struct block_ { uint64_t v[ARGON2_QWORDS_IN_BLOCK]; } block; + +/*****************Functions that work with the block******************/ + +/* Initialize each byte of the block with @in */ +void init_block_value(block *b, uint8_t in); + +/* Copy block @src to block @dst */ +void copy_block(block *dst, const block *src); + +/* XOR @src onto @dst bytewise */ +void xor_block(block *dst, const block *src); + +/* + * Argon2 instance: memory pointer, number of passes, amount of memory, type, + * and derived values. + * Used to evaluate the number and location of blocks to construct in each + * thread + */ +typedef struct Argon2_instance_t { + block *memory; /* Memory pointer */ + uint32_t version; + uint32_t passes; /* Number of passes */ + uint32_t memory_blocks; /* Number of blocks in memory */ + uint32_t segment_length; + uint32_t lane_length; + uint32_t lanes; + uint32_t threads; + argon2_type type; + int print_internals; /* whether to print the memory blocks */ + argon2_context *context_ptr; /* points back to original context */ +} argon2_instance_t; + +/* + * Argon2 position: where we construct the block right now. Used to distribute + * work between threads. + */ +typedef struct Argon2_position_t { + uint32_t pass; + uint32_t lane; + uint8_t slice; + uint32_t index; +} argon2_position_t; + +/*Struct that holds the inputs for thread handling FillSegment*/ +typedef struct Argon2_thread_data { + argon2_instance_t *instance_ptr; + argon2_position_t pos; +} argon2_thread_data; + +/*************************Argon2 core functions********************************/ + +/* Allocates memory to the given pointer, uses the appropriate allocator as + * specified in the context. Total allocated memory is num*size. + * @param context argon2_context which specifies the allocator + * @param memory pointer to the pointer to the memory + * @param size the size in bytes for each element to be allocated + * @param num the number of elements to be allocated + * @return ARGON2_OK if @memory is a valid pointer and memory is allocated + */ +int allocate_memory(const argon2_context *context, uint8_t **memory, + size_t num, size_t size); + +/* + * Frees memory at the given pointer, uses the appropriate deallocator as + * specified in the context. Also cleans the memory using clear_internal_memory. + * @param context argon2_context which specifies the deallocator + * @param memory pointer to buffer to be freed + * @param size the size in bytes for each element to be deallocated + * @param num the number of elements to be deallocated + */ +void free_memory(const argon2_context *context, uint8_t *memory, + size_t num, size_t size); + +/* Function that securely cleans the memory. This ignores any flags set + * regarding clearing memory. Usually one just calls clear_internal_memory. + * @param mem Pointer to the memory + * @param s Memory size in bytes + */ +void secure_wipe_memory(void *v, size_t n); + +/* Function that securely clears the memory if FLAG_clear_internal_memory is + * set. If the flag isn't set, this function does nothing. + * @param mem Pointer to the memory + * @param s Memory size in bytes + */ +void clear_internal_memory(void *v, size_t n); + +/* + * Computes absolute position of reference block in the lane following a skewed + * distribution and using a pseudo-random value as input + * @param instance Pointer to the current instance + * @param position Pointer to the current position + * @param pseudo_rand 32-bit pseudo-random value used to determine the position + * @param same_lane Indicates if the block will be taken from the current lane. + * If so we can reference the current segment + * @pre All pointers must be valid + */ +uint32_t index_alpha(const argon2_instance_t *instance, + const argon2_position_t *position, uint32_t pseudo_rand, + int same_lane); + +/* + * Function that validates all inputs against predefined restrictions and return + * an error code + * @param context Pointer to current Argon2 context + * @return ARGON2_OK if everything is all right, otherwise one of error codes + * (all defined in <argon2.h> + */ +int validate_inputs(const argon2_context *context); + +/* + * Hashes all the inputs into @a blockhash[PREHASH_DIGEST_LENGTH], clears + * password and secret if needed + * @param context Pointer to the Argon2 internal structure containing memory + * pointer, and parameters for time and space requirements. + * @param blockhash Buffer for pre-hashing digest + * @param type Argon2 type + * @pre @a blockhash must have at least @a PREHASH_DIGEST_LENGTH bytes + * allocated + */ +void initial_hash(uint8_t *blockhash, argon2_context *context, + argon2_type type); + +/* + * Function creates first 2 blocks per lane + * @param instance Pointer to the current instance + * @param blockhash Pointer to the pre-hashing digest + * @pre blockhash must point to @a PREHASH_SEED_LENGTH allocated values + */ +void fill_first_blocks(uint8_t *blockhash, const argon2_instance_t *instance); + +/* + * Function allocates memory, hashes the inputs with Blake, and creates first + * two blocks. Returns the pointer to the main memory with 2 blocks per lane + * initialized + * @param context Pointer to the Argon2 internal structure containing memory + * pointer, and parameters for time and space requirements. + * @param instance Current Argon2 instance + * @return Zero if successful, -1 if memory failed to allocate. @context->state + * will be modified if successful. + */ +int initialize(argon2_instance_t *instance, argon2_context *context); + +/* + * XORing the last block of each lane, hashing it, making the tag. Deallocates + * the memory. + * @param context Pointer to current Argon2 context (use only the out parameters + * from it) + * @param instance Pointer to current instance of Argon2 + * @pre instance->state must point to necessary amount of memory + * @pre context->out must point to outlen bytes of memory + * @pre if context->free_cbk is not NULL, it should point to a function that + * deallocates memory + */ +void finalize(const argon2_context *context, argon2_instance_t *instance); + +/* + * Function that fills the segment using previous segments also from other + * threads + * @param context current context + * @param instance Pointer to the current instance + * @param position Current position + * @pre all block pointers must be valid + */ +void fill_segment(const argon2_instance_t *instance, + argon2_position_t position); + +/* + * Function that fills the entire memory t_cost times based on the first two + * blocks in each lane + * @param instance Pointer to the current instance + * @return ARGON2_OK if successful, @context->state + */ +int fill_memory_blocks(argon2_instance_t *instance); + +#endif diff --git a/lib/argon2/ref.c b/lib/argon2/ref.c new file mode 100644 index 00000000000..10e45eba64e --- /dev/null +++ b/lib/argon2/ref.c @@ -0,0 +1,194 @@ +/* + * Argon2 reference source code package - reference C implementations + * + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves + * + * You may use this work under the terms of a Creative Commons CC0 1.0 + * License/Waiver or the Apache Public License 2.0, at your option. The terms of + * these licenses can be found at: + * + * - CC0 1.0 Universal : https://creativecommons.org/publicdomain/zero/1.0 + * - Apache 2.0 : https://www.apache.org/licenses/LICENSE-2.0 + * + * You should have received a copy of both of these licenses along with this + * software. If not, they may be obtained at the above URLs. + */ + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#include "argon2.h" +#include "core.h" + +#include "blake2/blamka-round-ref.h" +#include "blake2/blake2-impl.h" +#include "blake2/blake2.h" + + +/* + * Function fills a new memory block and optionally XORs the old block over the new one. + * @next_block must be initialized. + * @param prev_block Pointer to the previous block + * @param ref_block Pointer to the reference block + * @param next_block Pointer to the block to be constructed + * @param with_xor Whether to XOR into the new block (1) or just overwrite (0) + * @pre all block pointers must be valid + */ +static void fill_block(const block *prev_block, const block *ref_block, + block *next_block, int with_xor) { + block blockR, block_tmp; + unsigned i; + + copy_block(&blockR, ref_block); + xor_block(&blockR, prev_block); + copy_block(&block_tmp, &blockR); + /* Now blockR = ref_block + prev_block and block_tmp = ref_block + prev_block */ + if (with_xor) { + /* Saving the next block contents for XOR over: */ + xor_block(&block_tmp, next_block); + /* Now blockR = ref_block + prev_block and + block_tmp = ref_block + prev_block + next_block */ + } + + /* Apply Blake2 on columns of 64-bit words: (0,1,...,15) , then + (16,17,..31)... finally (112,113,...127) */ + for (i = 0; i < 8; ++i) { + BLAKE2_ROUND_NOMSG( + blockR.v[16 * i], blockR.v[16 * i + 1], blockR.v[16 * i + 2], + blockR.v[16 * i + 3], blockR.v[16 * i + 4], blockR.v[16 * i + 5], + blockR.v[16 * i + 6], blockR.v[16 * i + 7], blockR.v[16 * i + 8], + blockR.v[16 * i + 9], blockR.v[16 * i + 10], blockR.v[16 * i + 11], + blockR.v[16 * i + 12], blockR.v[16 * i + 13], blockR.v[16 * i + 14], + blockR.v[16 * i + 15]); + } + + /* Apply Blake2 on rows of 64-bit words: (0,1,16,17,...112,113), then + (2,3,18,19,...,114,115).. finally (14,15,30,31,...,126,127) */ + for (i = 0; i < 8; i++) { + BLAKE2_ROUND_NOMSG( + blockR.v[2 * i], blockR.v[2 * i + 1], blockR.v[2 * i + 16], + blockR.v[2 * i + 17], blockR.v[2 * i + 32], blockR.v[2 * i + 33], + blockR.v[2 * i + 48], blockR.v[2 * i + 49], blockR.v[2 * i + 64], + blockR.v[2 * i + 65], blockR.v[2 * i + 80], blockR.v[2 * i + 81], + blockR.v[2 * i + 96], blockR.v[2 * i + 97], blockR.v[2 * i + 112], + blockR.v[2 * i + 113]); + } + + copy_block(next_block, &block_tmp); + xor_block(next_block, &blockR); +} + +static void next_addresses(block *address_block, block *input_block, + const block *zero_block) { + input_block->v[6]++; + fill_block(zero_block, input_block, address_block, 0); + fill_block(zero_block, address_block, address_block, 0); +} + +void fill_segment(const argon2_instance_t *instance, + argon2_position_t position) { + block *ref_block = NULL, *curr_block = NULL; + block address_block, input_block, zero_block; + uint64_t pseudo_rand, ref_index, ref_lane; + uint32_t prev_offset, curr_offset; + uint32_t starting_index; + uint32_t i; + int data_independent_addressing; + + if (instance == NULL) { + return; + } + + data_independent_addressing = + (instance->type == Argon2_i) || + (instance->type == Argon2_id && (position.pass == 0) && + (position.slice < ARGON2_SYNC_POINTS / 2)); + + if (data_independent_addressing) { + init_block_value(&zero_block, 0); + init_block_value(&input_block, 0); + + input_block.v[0] = position.pass; + input_block.v[1] = position.lane; + input_block.v[2] = position.slice; + input_block.v[3] = instance->memory_blocks; + input_block.v[4] = instance->passes; + input_block.v[5] = instance->type; + } + + starting_index = 0; + + if ((0 == position.pass) && (0 == position.slice)) { + starting_index = 2; /* we have already generated the first two blocks */ + + /* Don't forget to generate the first block of addresses: */ + if (data_independent_addressing) { + next_addresses(&address_block, &input_block, &zero_block); + } + } + + /* Offset of the current block */ + curr_offset = position.lane * instance->lane_length + + position.slice * instance->segment_length + starting_index; + + if (0 == curr_offset % instance->lane_length) { + /* Last block in this lane */ + prev_offset = curr_offset + instance->lane_length - 1; + } else { + /* Previous block */ + prev_offset = curr_offset - 1; + } + + for (i = starting_index; i < instance->segment_length; + ++i, ++curr_offset, ++prev_offset) { + /*1.1 Rotating prev_offset if needed */ + if (curr_offset % instance->lane_length == 1) { + prev_offset = curr_offset - 1; + } + + /* 1.2 Computing the index of the reference block */ + /* 1.2.1 Taking pseudo-random value from the previous block */ + if (data_independent_addressing) { + if (i % ARGON2_ADDRESSES_IN_BLOCK == 0) { + next_addresses(&address_block, &input_block, &zero_block); + } + pseudo_rand = address_block.v[i % ARGON2_ADDRESSES_IN_BLOCK]; + } else { + pseudo_rand = instance->memory[prev_offset].v[0]; + } + + /* 1.2.2 Computing the lane of the reference block */ + ref_lane = ((pseudo_rand >> 32)) % instance->lanes; + + if ((position.pass == 0) && (position.slice == 0)) { + /* Can not reference other lanes yet */ + ref_lane = position.lane; + } + + /* 1.2.3 Computing the number of possible reference block within the + * lane. + */ + position.index = i; + ref_index = index_alpha(instance, &position, pseudo_rand & 0xFFFFFFFF, + ref_lane == position.lane); + + /* 2 Creating a new block */ + ref_block = + instance->memory + instance->lane_length * ref_lane + ref_index; + curr_block = instance->memory + curr_offset; + if (ARGON2_VERSION_10 == instance->version) { + /* version 1.2.1 and earlier: overwrite, not XOR */ + fill_block(instance->memory + prev_offset, ref_block, curr_block, 0); + } else { + if(0 == position.pass) { + fill_block(instance->memory + prev_offset, ref_block, + curr_block, 0); + } else { + fill_block(instance->memory + prev_offset, ref_block, + curr_block, 1); + } + } + } +} -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Rename argon.c to argon_wrapper.c so we can use 'argon' as the library name. Move the include file into the normal place. Add SPDX tags but otherwise keep the files as is. The code style uses spaces instead of tabs and has other differences with U-Boot Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- {lib/argon2 => include}/argon2.h | 15 ++++++-- lib/argon2/Makefile | 10 ++++++ lib/argon2/{argon2.c => argon2_wrapper.c} | 25 +++++++++++--- lib/argon2/blake2/blake2-impl.h | 10 +++--- lib/argon2/blake2/blake2.h | 1 + lib/argon2/blake2/blake2b.c | 7 ++-- lib/argon2/blake2/blamka-round-ref.h | 1 + lib/argon2/core.c | 42 +++-------------------- lib/argon2/core.h | 1 + lib/argon2/ref.c | 7 ++-- 10 files changed, 65 insertions(+), 54 deletions(-) rename {lib/argon2 => include}/argon2.h (98%) create mode 100644 lib/argon2/Makefile rename lib/argon2/{argon2.c => argon2_wrapper.c} (95%) diff --git a/lib/argon2/argon2.h b/include/argon2.h similarity index 98% rename from lib/argon2/argon2.h rename to include/argon2.h index 3980bb352f2..977739f555b 100644 --- a/lib/argon2/argon2.h +++ b/include/argon2.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 */ /* * Argon2 reference source code package - reference C implementations * @@ -18,10 +19,20 @@ #ifndef ARGON2_H #define ARGON2_H -#include <stdint.h> -#include <stddef.h> +#include <linux/types.h> #include <limits.h> +/* U-Boot: Define missing integer constant macros */ +#ifndef UINT32_C +#define UINT32_C(c) c ## U +#endif +#ifndef UINT64_C +#define UINT64_C(c) c ## ULL +#endif + +/* U-Boot: Disable threading */ +#define ARGON2_NO_THREADS + #if defined(__cplusplus) extern "C" { #endif diff --git a/lib/argon2/Makefile b/lib/argon2/Makefile new file mode 100644 index 00000000000..c9f729d27e6 --- /dev/null +++ b/lib/argon2/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2025 Canonical Ltd +# +# Argon2 password hashing library +# Based on PHC winner: https://github.com/P-H-C/phc-winner-argon2 + +obj-$(CONFIG_ARGON2) += argon2.o + +argon2-y := argon2_wrapper.o core.o ref.o blake2/blake2b.o diff --git a/lib/argon2/argon2.c b/lib/argon2/argon2_wrapper.c similarity index 95% rename from lib/argon2/argon2.c rename to lib/argon2/argon2_wrapper.c index 34da3d6b4ac..2c1882165f1 100644 --- a/lib/argon2/argon2.c +++ b/lib/argon2/argon2_wrapper.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 /* * Argon2 reference source code package - reference C implementations * @@ -15,12 +16,12 @@ * software. If not, they may be obtained at the above URLs. */ -#include <string.h> -#include <stdlib.h> -#include <stdio.h> +/* U-Boot: Use U-Boot headers */ +#include <linux/string.h> +#include <linux/types.h> +#include <malloc.h> #include "argon2.h" -#include "encoding.h" #include "core.h" const char *argon2_type2string(argon2_type type, int uppercase) { @@ -161,6 +162,15 @@ int argon2_hash(const uint32_t t_cost, const uint32_t m_cost, memcpy(hash, out, hashlen); } + /* U-Boot: encoding not supported (requires encoding.c) */ +#ifdef __UBOOT__ + /* Return error if encoding requested */ + if (encoded && encodedlen) { + clear_internal_memory(out, hashlen); + free(out); + return ARGON2_ENCODING_FAIL; + } +#else /* if encoding requested, write it */ if (encoded && encodedlen) { if (encode_string(encoded, encodedlen, &context, type) != ARGON2_OK) { @@ -170,6 +180,7 @@ int argon2_hash(const uint32_t t_cost, const uint32_t m_cost, return ARGON2_ENCODING_FAIL; } } +#endif clear_internal_memory(out, hashlen); free(out); @@ -236,6 +247,8 @@ int argon2id_hash_raw(const uint32_t t_cost, const uint32_t m_cost, ARGON2_VERSION_NUMBER); } +/* U-Boot: verify functions not needed */ +#ifndef __UBOOT__ static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) { size_t i; uint8_t d = 0U; @@ -364,6 +377,7 @@ int argon2i_verify_ctx(argon2_context *context, const char *hash) { int argon2id_verify_ctx(argon2_context *context, const char *hash) { return argon2_verify_ctx(context, hash, Argon2_id); } +#endif /* U-Boot: verify functions */ const char *argon2_error_message(int error_code) { switch (error_code) { @@ -444,9 +458,12 @@ const char *argon2_error_message(int error_code) { } } +/* U-Boot: encodedlen not needed */ +#ifndef __UBOOT__ size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost, uint32_t parallelism, uint32_t saltlen, uint32_t hashlen, argon2_type type) { return strlen("$$v=$m=,t=,p=$$") + strlen(argon2_type2string(type, 0)) + numlen(t_cost) + numlen(m_cost) + numlen(parallelism) + b64len(saltlen) + b64len(hashlen) + numlen(ARGON2_VERSION_NUMBER) + 1; } +#endif /* U-Boot: encodedlen */ diff --git a/lib/argon2/blake2/blake2-impl.h b/lib/argon2/blake2/blake2-impl.h index 86d0d5cfa9b..5cdb2fc8b4f 100644 --- a/lib/argon2/blake2/blake2-impl.h +++ b/lib/argon2/blake2/blake2-impl.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 */ /* * Argon2 reference source code package - reference C implementations * @@ -18,12 +19,11 @@ #ifndef PORTABLE_BLAKE2_IMPL_H #define PORTABLE_BLAKE2_IMPL_H -#include <stdint.h> -#include <string.h> +/* U-Boot includes */ +#include <linux/types.h> +#include <linux/string.h> -#ifdef _WIN32 -#define BLAKE2_INLINE __inline -#elif defined(__GNUC__) || defined(__clang__) +#if defined(__GNUC__) || defined(__clang__) #define BLAKE2_INLINE __inline__ #else #define BLAKE2_INLINE diff --git a/lib/argon2/blake2/blake2.h b/lib/argon2/blake2/blake2.h index 501c6a3186d..6c7bb6625fe 100644 --- a/lib/argon2/blake2/blake2.h +++ b/lib/argon2/blake2/blake2.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 */ /* * Argon2 reference source code package - reference C implementations * diff --git a/lib/argon2/blake2/blake2b.c b/lib/argon2/blake2/blake2b.c index 3b519ddb4d0..f0e78a5c671 100644 --- a/lib/argon2/blake2/blake2b.c +++ b/lib/argon2/blake2/blake2b.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 /* * Argon2 reference source code package - reference C implementations * @@ -15,9 +16,9 @@ * software. If not, they may be obtained at the above URLs. */ -#include <stdint.h> -#include <string.h> -#include <stdio.h> +/* U-Boot includes */ +#include <linux/types.h> +#include <linux/string.h> #include "blake2.h" #include "blake2-impl.h" diff --git a/lib/argon2/blake2/blamka-round-ref.h b/lib/argon2/blake2/blamka-round-ref.h index 16cfc1c74af..3b60c384b38 100644 --- a/lib/argon2/blake2/blamka-round-ref.h +++ b/lib/argon2/blake2/blamka-round-ref.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 */ /* * Argon2 reference source code package - reference C implementations * diff --git a/lib/argon2/core.c b/lib/argon2/core.c index e697882eb96..4662cf05bf5 100644 --- a/lib/argon2/core.c +++ b/lib/argon2/core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 /* * Argon2 reference source code package - reference C implementations * @@ -15,32 +16,14 @@ * software. If not, they may be obtained at the above URLs. */ -/*For memory wiping*/ -#ifdef _WIN32 -#include <windows.h> -#include <winbase.h> /* For SecureZeroMemory */ -#endif -#if defined __STDC_LIB_EXT1__ -#define __STDC_WANT_LIB_EXT1__ 1 -#endif -#define VC_GE_2005(version) (version >= 1400) - -/* for explicit_bzero() on glibc */ -#define _DEFAULT_SOURCE - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> +/* U-Boot includes */ +#include <linux/string.h> +#include <malloc.h> #include "core.h" -#include "thread.h" #include "blake2/blake2.h" #include "blake2/blake2-impl.h" -#ifdef GENKAT -#include "genkat.h" -#endif - #if defined(__clang__) #if __has_attribute(optnone) #define NOT_OPTIMIZED __attribute__((optnone)) @@ -123,25 +106,10 @@ void free_memory(const argon2_context *context, uint8_t *memory, } } -#if defined(__OpenBSD__) -#define HAVE_EXPLICIT_BZERO 1 -#elif defined(__GLIBC__) && defined(__GLIBC_PREREQ) -#if __GLIBC_PREREQ(2,25) -#define HAVE_EXPLICIT_BZERO 1 -#endif -#endif - void NOT_OPTIMIZED secure_wipe_memory(void *v, size_t n) { -#if defined(_MSC_VER) && VC_GE_2005(_MSC_VER) || defined(__MINGW32__) - SecureZeroMemory(v, n); -#elif defined memset_s - memset_s(v, n, 0, n); -#elif defined(HAVE_EXPLICIT_BZERO) - explicit_bzero(v, n); -#else + /* Use volatile pointer to prevent compiler optimization */ static void *(*const volatile memset_sec)(void *, int, size_t) = &memset; memset_sec(v, 0, n); -#endif } /* Memory clear flag defaults to true. */ diff --git a/lib/argon2/core.h b/lib/argon2/core.h index 59e25646cbc..0c27fc6541d 100644 --- a/lib/argon2/core.h +++ b/lib/argon2/core.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 */ /* * Argon2 reference source code package - reference C implementations * diff --git a/lib/argon2/ref.c b/lib/argon2/ref.c index 10e45eba64e..d67292c856f 100644 --- a/lib/argon2/ref.c +++ b/lib/argon2/ref.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 OR CC0-1.0 /* * Argon2 reference source code package - reference C implementations * @@ -15,9 +16,9 @@ * software. If not, they may be obtained at the above URLs. */ -#include <stdint.h> -#include <string.h> -#include <stdlib.h> +/* U-Boot includes */ +#include <linux/types.h> +#include <linux/string.h> #include "argon2.h" #include "core.h" -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add a Kconfig optiion to enable this library and add it to the lib/ Makefile, being careful to avoid a conflict with the existing blake2b implementation. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/misc/Kconfig | 2 +- fs/btrfs/Kconfig | 2 +- lib/Kconfig | 14 +++++++++++++- lib/Makefile | 4 ++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index a352fa5fee0..7a217ad055d 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -37,7 +37,7 @@ config TKEY bool "TKey security token support" depends on DM default y if SANDBOX - select BLAKE2 + select BLAKE2 if !ARGON2 help Enable driver model support for Tillitis TKey security tokens. This provides a common interface for TKey operations including diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig index e31afe595f3..c6430f83452 100644 --- a/fs/btrfs/Kconfig +++ b/fs/btrfs/Kconfig @@ -6,7 +6,7 @@ config FS_BTRFS select ZSTD select RBTREE select SHA256 - select BLAKE2 + select BLAKE2 if !ARGON2 help This provides a single-device read-only BTRFS support. BTRFS is a next-generation Linux file system based on the copy-on-write diff --git a/lib/Kconfig b/lib/Kconfig index c8bf4b4b049..662b1a44d45 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -542,11 +542,12 @@ menu "Hashing Support" config BLAKE2 bool "Enable BLAKE2 support" + depends on !ARGON2 help This option enables support of hashing using BLAKE2B algorithm. The hash is calculated in software. The BLAKE2 algorithm produces a hash value (digest) between 1 and - 64 bytes. + 64 bytes. Note: ARGON2 includes its own BLAKE2 implementation. config SHA1 bool "Enable SHA1 support" @@ -983,6 +984,17 @@ config JSON printing functions. JSON is used for structured data representation, such as LUKS2 metadata. +config ARGON2 + bool "Enable Argon2 password hashing" + help + This enables the Argon2 password hashing algorithm, winner of the + Password Hashing Competition (PHC). Argon2 is used for key derivation + in LUKS2 encrypted volumes. It provides better resistance to GPU + cracking attacks compared to PBKDF2. + + Note: This option includes its own BLAKE2 implementation and is + mutually exclusive with CONFIG_BLAKE2. + config OF_LIBFDT bool "Enable the FDT library" default y if OF_CONTROL diff --git a/lib/Makefile b/lib/Makefile index 71c9c0d1766..5cbf3071f96 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -80,7 +80,10 @@ obj-$(CONFIG_$(PHASE_)ACPI) += acpi/ obj-$(CONFIG_ECDSA) += ecdsa/ obj-$(CONFIG_$(PHASE_)RSA) += rsa/ obj-$(CONFIG_HASH) += hash-checksum.o + +# argon2 provides its own blake2b; just build blake2s when ARGON2 is enabled obj-$(CONFIG_BLAKE2) += blake2/blake2b.o blake2/blake2s.o +obj-$(CONFIG_ARGON2) += blake2/blake2s.o obj-$(CONFIG_$(PHASE_)MD5_LEGACY) += md5.o obj-$(CONFIG_$(PHASE_)SHA1_LEGACY) += sha1.o @@ -89,6 +92,7 @@ obj-$(CONFIG_$(PHASE_)SHA256_LEGACY) += sha256.o obj-$(CONFIG_$(PHASE_)SHA512_LEGACY) += sha512.o obj-$(CONFIG_CRYPT_PW) += crypt/ +obj-$(CONFIG_ARGON2) += argon2/ obj-$(CONFIG_$(PHASE_)ASN1_DECODER_LEGACY) += asn1_decoder.o obj-$(CONFIG_$(PHASE_)ZLIB) += zlib/ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> This is very long and the 'encrypt' part is implied by the passphrase. Shorten it to just 'passphrase'. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- doc/usage/luks.rst | 2 +- test/py/img/common.py | 2 +- test/py/tests/fs_helper.py | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/usage/luks.rst b/doc/usage/luks.rst index a87abe140db..24be8d1ea81 100644 --- a/doc/usage/luks.rst +++ b/doc/usage/luks.rst @@ -232,7 +232,7 @@ See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class:: # Create encrypted filesystem with FsHelper(config, 'ext4', 30, 'test', part_mb=60, - encrypt_passphrase='mypassword') as fsh: + passphrase='mypassword') as fsh: # Add files to fsh.srcdir with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: f.write('Hello from LUKS!\n') diff --git a/test/py/img/common.py b/test/py/img/common.py index 3b3fdb2734b..f5a7fcba804 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -82,7 +82,7 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, part_mb=part2_size, - encrypt_passphrase='test' if use_fde else None, + passphrase='test' if use_fde else None, luks_version=use_fde if use_fde else 2) ext4.setup() diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index 914de09e381..4812d3f053b 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -41,7 +41,7 @@ class FsHelper: To create an encrypted LUKS2 partition (default): with FsHelper(ubman.config, 'ext4', 10, 'mmc1', - encrypt_passphrase='test') as fsh: + passphrase='test') as fsh: # create files in the fsh.srcdir directory fsh.mk_fs() # Creates and encrypts the filesystem with LUKS2 ... @@ -49,7 +49,7 @@ class FsHelper: To create an encrypted LUKS1 partition: with FsHelper(ubman.config, 'ext4', 10, 'mmc1', - encrypt_passphrase='test', luks_version=1) as fsh: + passphrase='test', luks_version=1) as fsh: # create files in the fsh.srcdir directory fsh.mk_fs() # Creates and encrypts the filesystem with LUKS1 ... @@ -59,7 +59,7 @@ class FsHelper: default value but can be overwritten """ def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, - encrypt_passphrase=None, luks_version=2): + passphrase=None, luks_version=2): """Set up a new object Args: @@ -71,7 +71,7 @@ class FsHelper: part_mb (int, optional): Size of partition in MB. If None, defaults to size_mb. This can be used to make the partition larger than the filesystem, to create space for disk-encryption metadata - encrypt_passphrase (str, optional): If provided, encrypt the + passphrase (str, optional): If provided, encrypt the filesystem with LUKS using this passphrase luks_version (int): LUKS version to use (1 or 2). Defaults to 2. """ @@ -85,7 +85,7 @@ class FsHelper: self.partition_mb = part_mb if part_mb is not None else size_mb self.prefix = prefix self.quiet = True - self.encrypt_passphrase = encrypt_passphrase + self.passphrase = passphrase self.luks_version = luks_version # Use a default filename; the caller can adjust it @@ -159,8 +159,8 @@ class FsHelper: shell=True) # Encrypt the filesystem if requested - if self.encrypt_passphrase: - self.encrypt_luks(self.encrypt_passphrase) + if self.passphrase: + self.encrypt_luks(self.passphrase) def setup(self): """Set up the srcdir ready to receive files""" -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> For LUKS version 2, argon is normally used in preference to pbkdf2. Add an argument to specify this when creating a filesystem. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/tests/fs_helper.py | 40 ++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/test/py/tests/fs_helper.py b/test/py/tests/fs_helper.py index 4812d3f053b..d88cc270b95 100644 --- a/test/py/tests/fs_helper.py +++ b/test/py/tests/fs_helper.py @@ -54,12 +54,20 @@ class FsHelper: fsh.mk_fs() # Creates and encrypts the filesystem with LUKS1 ... + To create an encrypted LUKS2 partition with Argon2id: + + with FsHelper(ubman.config, 'ext4', 10, 'mmc1', + passphrase='test', luks_kdf='argon2id') as fsh: + # create files in the fsh.srcdir directory + fsh.mk_fs() # Creates and encrypts the FS with LUKS2+Argon2 + ... + Properties: fs_img (str): Filename for the filesystem image; this is set to a default value but can be overwritten """ def __init__(self, config, fs_type, size_mb, prefix, part_mb=None, - passphrase=None, luks_version=2): + passphrase=None, luks_version=2, luks_kdf='pbkdf2'): """Set up a new object Args: @@ -74,6 +82,8 @@ class FsHelper: passphrase (str, optional): If provided, encrypt the filesystem with LUKS using this passphrase luks_version (int): LUKS version to use (1 or 2). Defaults to 2. + luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or + 'argon2id'. Defaults to 'pbkdf2'. Ignored for LUKS1. """ if ('fat' not in fs_type and 'ext' not in fs_type and fs_type not in ['exfat', 'fs_generic']): @@ -87,6 +97,7 @@ class FsHelper: self.quiet = True self.passphrase = passphrase self.luks_version = luks_version + self.luks_kdf = luks_kdf # Use a default filename; the caller can adjust it leaf = f'{prefix}.{fs_type}.img' @@ -238,13 +249,26 @@ class FsHelper: try: # Format as LUKS (version determined by luks_type) - run(['cryptsetup', 'luksFormat', - '--type', luks_type, - '--cipher', cipher, - '--key-size', key_size_str, - '--hash', hash_alg, - '--iter-time', '10', # Very fast for testing (low security) - luks_img], + cmd = ['cryptsetup', 'luksFormat', + '--type', luks_type, + '--cipher', cipher, + '--key-size', key_size_str, + '--hash', hash_alg, + '--iter-time', '10'] # Very fast for testing (low security) + + # For LUKS2, specify the KDF (pbkdf2 or argon2id) + if self.luks_version == 2: + cmd.extend(['--pbkdf', self.luks_kdf]) + # For Argon2, use low memory/time settings suitable for testing + if self.luks_kdf == 'argon2id': + cmd.extend([ + '--pbkdf-memory', '65536', # 64MB + '--pbkdf-parallel', '4', + ]) + + cmd.append(luks_img) + + run(cmd, input=f'{passphrase}\n'.encode(), stdout=DEVNULL if self.quiet else None, stderr=DEVNULL if self.quiet else None, -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Use the more common argon2id algorithm for this disk so that we can test the implementation. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- test/py/img/common.py | 7 +++++-- test/py/img/ubuntu.py | 6 ++++-- test/py/tests/test_ut.py | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/test/py/img/common.py b/test/py/img/common.py index f5a7fcba804..74ea04771c7 100644 --- a/test/py/img/common.py +++ b/test/py/img/common.py @@ -33,7 +33,7 @@ def copy_partition(ubman, fsfile, outname): def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, - script, part2_size=1, use_fde=0): + script, part2_size=1, use_fde=0, luks_kdf='pbkdf2'): """Create a 20MB disk image with a single FAT partition Args: @@ -47,6 +47,8 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, script (str): Script to place in the extlinux.conf file part2_size (int): Size of second partition in MB (default: 1) use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) + luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. + Defaults to 'pbkdf2'. Ignored for LUKS1. """ fsh = FsHelper(config, 'vfat', 18, prefix=basename) fsh.setup() @@ -83,7 +85,8 @@ def setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, ext4 = FsHelper(config, 'ext4', max(1, part2_size - 30), prefix=basename, part_mb=part2_size, passphrase='test' if use_fde else None, - luks_version=use_fde if use_fde else 2) + luks_version=use_fde if use_fde else 2, + luks_kdf=luks_kdf) ext4.setup() bindir = os.path.join(ext4.srcdir, 'bin') diff --git a/test/py/img/ubuntu.py b/test/py/img/ubuntu.py index b783f7eb3cf..243fa38d021 100644 --- a/test/py/img/ubuntu.py +++ b/test/py/img/ubuntu.py @@ -7,7 +7,7 @@ from img.common import setup_extlinux_image def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', - use_fde=0): + use_fde=0, luks_kdf='pbkdf2'): """Create a Ubuntu disk image with a FAT partition and ext4 partition This creates a FAT partition containing extlinux files, kernel, etc. and a @@ -19,6 +19,8 @@ def setup_ubuntu_image(config, log, devnum, basename, version='24.04.1 LTS', devnum (int): Device number to use, e.g. 1 basename (str): Base name to use in the filename, e.g. 'mmc' use_fde (int): LUKS version for full-disk encryption (0=none, 1=LUKS1, 2=LUKS2) + luks_kdf (str): Key derivation function for LUKS2: 'pbkdf2' or 'argon2id'. + Defaults to 'pbkdf2'. Ignored for LUKS1. """ vmlinux = 'vmlinuz-6.8.0-53-generic' initrd = 'initrd.img-6.8.0-53-generic' @@ -50,4 +52,4 @@ label l0r ''' % ((version, vmlinux, initrd) * 2) setup_extlinux_image(config, log, devnum, basename, vmlinux, initrd, dtbdir, script, part2_size=60 if use_fde else 1, - use_fde=use_fde) + use_fde=use_fde, luks_kdf=luks_kdf) diff --git a/test/py/tests/test_ut.py b/test/py/tests/test_ut.py index 94d98b3b73b..e2b4d49a2e0 100644 --- a/test/py/tests/test_ut.py +++ b/test/py/tests/test_ut.py @@ -84,7 +84,8 @@ def test_ut_dm_init_bootstd(u_boot_config, u_boot_log): setup_localboot_image(u_boot_config, u_boot_log) setup_vbe_image(u_boot_config, u_boot_log) setup_ubuntu_image(u_boot_config, u_boot_log, 11, 'mmc', use_fde=1) - setup_ubuntu_image(u_boot_config, u_boot_log, 12, 'mmc', use_fde=2) + setup_ubuntu_image(u_boot_config, u_boot_log, 12, 'mmc', use_fde=2, + luks_kdf='argon2id') def test_ut(ubman, ut_subtest): """Execute a "ut" subtest. -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Provide this function through an internal header, so that luks2 will be able to use it. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 5 +++-- drivers/block/luks_internal.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 drivers/block/luks_internal.h diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 4400f1cfd84..826fe062757 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -25,6 +25,7 @@ #include <mbedtls/pkcs5.h> #include <u-boot/sha256.h> #include <u-boot/sha512.h> +#include "luks_internal.h" int luks_get_version(struct udevice *blk, struct disk_partition *pinfo) { @@ -206,8 +207,8 @@ static int af_hash(struct hash_algo *algo, size_t key_size, u8 *block_buf) * @hash_spec: Hash algorithm name (e.g., "sha256") * Return: 0 on success, -ve on error */ -static int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, - const char *hash_spec) +int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, + const char *hash_spec) { struct hash_algo *algo; u8 block_buf[128]; diff --git a/drivers/block/luks_internal.h b/drivers/block/luks_internal.h new file mode 100644 index 00000000000..32714787550 --- /dev/null +++ b/drivers/block/luks_internal.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * LUKS (Linux Unified Key Setup) internal interfaces + * + * Copyright (C) 2025 Canonical Ltd + */ + +#ifndef __LUKS_INTERNAL_H__ +#define __LUKS_INTERNAL_H__ + +#include <hash.h> + +/** + * af_merge() - Merge anti-forensic split key into original key + * + * This performs the LUKS AF-merge operation to recover the original key from + * its AF-split representation. The algorithm XORs all stripes together, + * applying diffusion between each stripe. Used by both LUKS1 and LUKS2. + * + * @src: AF-split key material (key_size * stripes bytes) + * @dst: Output buffer for merged key (key_size bytes) + * @key_size: Size of the original key + * @stripes: Number of anti-forensic stripes + * @hash_spec: Hash algorithm name (e.g., "sha256") + * Return: 0 on success, -ve on error + */ +int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, + const char *hash_spec); + +#endif /* __LUKS_INTERNAL_H__ */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> In preparation for luks v2, make a few code-style tweaks: - shorter vars in some cases - 80cols in a few places - drop an unwanted blank line - use 'pass' instead of 'passphrase' - unnecessary assignments to NULL Signed-off-by: Simon Glass <simon.glass@canonical.com> --- drivers/block/luks.c | 112 +++++++++++++++++++++---------------------- include/luks.h | 4 +- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 826fe062757..05df5b12c53 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -33,7 +33,6 @@ int luks_get_version(struct udevice *blk, struct disk_partition *pinfo) ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buffer, desc->blksz); int version; - /* Read first block of the partition */ if (blk_read(blk, pinfo->start, 1, buffer) != 1) { log_debug("Error: failed to read LUKS header\n"); @@ -114,19 +113,19 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo) if (IS_ENABLED(CONFIG_JSON)) { u64 json_size; char *json_start; - int blocks; + int count; /* Read the full header to get JSON area */ - blocks = (hdr_size + desc->blksz - 1) / desc->blksz; - ALLOC_CACHE_ALIGN_BUFFER(unsigned char, full_hdr, blocks * desc->blksz); + count = (hdr_size + desc->blksz - 1) / desc->blksz; + ALLOC_CACHE_ALIGN_BUFFER(u8, hdr, count * desc->blksz); - if (blk_read(blk, pinfo->start, blocks, full_hdr) != blocks) { - printf("Error: failed to read full LUKS2 header\n"); + if (blk_read(blk, pinfo->start, count, hdr) != count) { + printf("Error: can't read full LUKS2 header\n"); return -EIO; } /* JSON starts after the 4096-byte binary header */ - json_start = (char *)(full_hdr + 4096); + json_start = (char *)(hdr + 4096); json_size = hdr_size - 4096; printf("\nJSON metadata (%llx bytes):\n", json_size); @@ -196,9 +195,9 @@ static int af_hash(struct hash_algo *algo, size_t key_size, u8 *block_buf) /** * af_merge() - Merge anti-forensic split key into original key * - * This performs the LUKS AF-merge operation to recover the original key from its - * AF-split representation. The algorithm XORs all stripes together, applying - * diffusion between each stripe. + * This performs the LUKS AF-merge operation to recover the original key from + * its AF-split representation. The algorithm XORs all stripes together, + * applying diffusion between each stripe. * * @src: AF-split key material (key_size * stripes bytes) * @dst: Output buffer for merged key (key_size bytes) @@ -248,25 +247,6 @@ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, return 0; } -/** - * try_keyslot() - Unlock a LUKS key slot with a passphrase - * - * @blk: Block device - * @pinfo: Partition information - * @hdr: LUKS header - * @slot_idx: Key slot index to try - * @passphrase: Passphrase to try - * @md_type: Hash algorithm type - * @key_size: Size of the key - * @derived_key: Buffer for derived key (key_size bytes) - * @km: Buffer for encrypted key material - * @km_blocks: Size of km buffer in blocks - * @split_key: Buffer for AF-split key - * @candidate_key: Buffer to receive decrypted master key - * - * Return: 0 on success (correct passphrase), -EPROTO on mbedtls error, -ve on - * other error - */ /** * essiv_decrypt() - Decrypt key material using ESSIV mode * @@ -282,8 +262,8 @@ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, * @km_blocks: Number of blocks of key material * @blksz: Block size in bytes */ -static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, - u8 *km, u8 *split_key, uint km_blocks, uint blksz) +static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, u8 *km, + u8 *split_key, uint km_blocks, uint blksz) { u8 essiv_expkey[AES256_EXPAND_KEY_LENGTH]; u8 essiv_key_material[SHA256_SUM_LEN]; @@ -333,14 +313,33 @@ static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, } } +/** + * try_keyslot() - Unlock a LUKS key slot with a passphrase + * + * @blk: Block device + * @pinfo: Partition information + * @hdr: LUKS header + * @slot_idx: Key slot index to try + * @pass: Passphrase to try + * @md_type: Hash algorithm type + * @key_size: Size of the key + * @derived_key: Buffer for derived key (key_size bytes) + * @km: Buffer for encrypted key material + * @km_blocks: Size of km buffer in blocks + * @split_key: Buffer for AF-split key + * @candidate_key: Buffer to receive decrypted master key + * + * Return: 0 on success (correct passphrase), -EPROTO on mbedtls error, -ve on + * other error + */ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, struct luks1_phdr *hdr, int slot_idx, - const char *passphrase, mbedtls_md_type_t md_type, + const char *pass, mbedtls_md_type_t md_type, uint key_size, u8 *derived_key, u8 *km, uint km_blocks, u8 *split_key, u8 *candidate_key) { struct luks1_keyslot *slot = &hdr->key_slot[slot_idx]; - uint iterations, km_offset, stripes, split_key_size; + uint iters, km_offset, stripes, split_key_size; struct blk_desc *desc = dev_get_uclass_plat(blk); u8 expkey[AES256_EXPAND_KEY_LENGTH]; u8 key_digest[LUKS_DIGESTSIZE]; @@ -353,21 +352,20 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug("trying key slot %d...\n", slot_idx); - iterations = be32_to_cpu(slot->iterations); + iters = be32_to_cpu(slot->iterations); km_offset = be32_to_cpu(slot->key_material_offset); stripes = be32_to_cpu(slot->stripes); split_key_size = key_size * stripes; /* Derive key from passphrase using PBKDF2 */ - log_debug("PBKDF2(pass '%s'[len %zu], ", passphrase, - strlen(passphrase)); + log_debug("PBKDF2(pass '%s'[len %zu], ", pass, strlen(pass)); log_debug_hex("salt[0-7]", (u8 *)slot->salt, 8); - log_debug("iter %u, keylen %u)\n", iterations, key_size); - ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)passphrase, - strlen(passphrase), + log_debug("iter %u, keylen %u)\n", iters, key_size); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)pass, + strlen(pass), (const u8 *)slot->salt, - LUKS_SALTSIZE, iterations, - key_size, derived_key); + LUKS_SALTSIZE, iters, key_size, + derived_key); if (ret) { log_debug("PBKDF2 failed: %d\n", ret); return -EPROTO; @@ -389,7 +387,6 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug_hex("input key (derived_key) full:", derived_key, key_size); aes_expand_key(derived_key, key_size * 8, expkey); - log_debug_hex("expanded key [0-15]:", expkey, 16); /* Decrypt with CBC mode: first check if ESSIV is used */ @@ -433,7 +430,7 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, log_debug_hex("mk_digest[0-7]", (u8 *)hdr->mk_digest, 8); /* Check if the digest matches */ - if (memcmp(key_digest, hdr->mk_digest, LUKS_DIGESTSIZE) == 0) { + if (!memcmp(key_digest, hdr->mk_digest, LUKS_DIGESTSIZE)) { log_debug("Uunlocked with key slot %d\n", slot_idx); return 0; } @@ -443,20 +440,18 @@ static int try_keyslot(struct udevice *blk, struct disk_partition *pinfo, } int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const char *passphrase, u8 *master_key, u32 *key_size) + const char *pass, u8 *master_key, u32 *key_size) { uint version, split_key_size, km_blocks, hdr_blocks; + u8 *split_key, *derived_key; struct hash_algo *hash_algo; + u8 candidate_key[128], *km; mbedtls_md_type_t md_type; struct luks1_phdr *hdr; struct blk_desc *desc; - u8 candidate_key[128]; - u8 *split_key = NULL; - u8 *derived_key = NULL; - u8 *km = NULL; - int i, ret = -EINVAL; + int i, ret; - if (!blk || !pinfo || !passphrase || !master_key || !key_size) + if (!blk || !pinfo || !pass || !master_key || !key_size) return -EINVAL; desc = dev_get_uclass_plat(blk); @@ -482,14 +477,15 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); if (version != LUKS_VERSION_1) { - log_debug("only LUKS1 decryption is currently supported\n"); + log_debug("unsupported LUKS version %d\n", version); return -ENOTSUPP; } hdr = (struct luks1_phdr *)buffer; /* Debug: show what we read from header */ - log_debug("Read header at sector %llu, mk_digest[0-7] ", (unsigned long long)pinfo->start); + log_debug("Read header at sector %llu, mk_digest[0-7] ", + (unsigned long long)pinfo->start); log_debug_hex("", (u8 *)hdr->mk_digest, 8); /* Verify cipher mode - only CBC supported */ @@ -518,7 +514,7 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, break; } } - if (stripes == 0) { + if (!stripes) { log_debug("no active key slots found\n"); return -ENOENT; } @@ -541,7 +537,7 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, /* Try each key slot */ for (i = 0; i < LUKS_NUMKEYS; i++) { - ret = try_keyslot(blk, pinfo, hdr, i, passphrase, md_type, + ret = try_keyslot(blk, pinfo, hdr, i, pass, md_type, *key_size, derived_key, km, km_blocks, split_key, candidate_key); @@ -586,12 +582,12 @@ out: * @master_key: Unlocked master key * @key_size: Size of the master key in bytes * @label: Label for the blkmap device - * @blkmap_dev: Output pointer for created blkmap device + * @blkmapp: Output pointer for created blkmap device * Return: 0 on success, -ve on error */ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, const u8 *master_key, u32 key_size, const char *label, - struct udevice **blkmap_dev) + struct udevice **blkmapp) { u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */ struct luks1_phdr *hdr; @@ -601,7 +597,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, bool use_essiv; int ret; - if (!blk || !pinfo || !master_key || !label || !blkmap_dev) + if (!blk || !pinfo || !master_key || !label || !blkmapp) return -EINVAL; desc = dev_get_uclass_plat(blk); @@ -651,7 +647,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, /* Wipe ESSIV key from stack */ if (use_essiv) memset(essiv_key, '\0', sizeof(essiv_key)); - *blkmap_dev = dev; + *blkmapp = dev; return 0; } diff --git a/include/luks.h b/include/luks.h index f8fda27e132..2c52cc48689 100644 --- a/include/luks.h +++ b/include/luks.h @@ -145,13 +145,13 @@ int luks_show_info(struct udevice *blk, struct disk_partition *pinfo); * * @blk: Block device * @pinfo: Partition information - * @passphrase: Passphrase to unlock the partition + * @pass: Passphrase to unlock the partition * @master_key: Buffer to receive the decrypted master key * @key_size: Size of the master_key buffer * Return: 0 on success, -ve on error */ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, - const char *passphrase, u8 *master_key, u32 *key_size); + const char *pass, u8 *master_key, u32 *key_size); /** * luks_create_blkmap() - Create a blkmap device for a LUKS partition -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add supports for luks v2 which is a more common version used on modern systems. This makes use of Argon2 and also the JSON->FDT parser. Enable this feature for sandbox, tidying up the defconfig while we are here. Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- configs/sandbox_defconfig | 4 +- drivers/block/Makefile | 2 +- drivers/block/luks.c | 116 +++- drivers/block/luks2.c | 974 ++++++++++++++++++++++++++++++++++ drivers/block/luks_internal.h | 13 + 5 files changed, 1100 insertions(+), 9 deletions(-) create mode 100644 drivers/block/luks2.c diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 6a4b3e4363a..006c6916af6 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -169,7 +169,6 @@ CONFIG_ADC=y CONFIG_ADC_SANDBOX=y CONFIG_AXI=y CONFIG_AXI_SANDBOX=y -CONFIG_BLKMAP=y CONFIG_SYS_IDE_MAXBUS=1 CONFIG_SYS_ATA_BASE_ADDR=0x100 CONFIG_SYS_ATA_STRIDE=4 @@ -177,6 +176,7 @@ CONFIG_SYS_ATA_DATA_OFFSET=0 CONFIG_SYS_ATA_REG_OFFSET=1 CONFIG_SYS_ATA_ALT_OFFSET=2 CONFIG_SYS_ATA_IDE0_OFFSET=0 +CONFIG_BLK_LUKS=y CONFIG_BOOTCOUNT_LIMIT=y CONFIG_DM_BOOTCOUNT=y CONFIG_DM_BOOTCOUNT_RTC=y @@ -362,12 +362,12 @@ CONFIG_ADDR_MAP=y CONFIG_PANIC_POWEROFF=y CONFIG_CMD_DHRYSTONE=y CONFIG_MBEDTLS_LIB=y -CONFIG_BLK_LUKS=y CONFIG_ECDSA=y CONFIG_ECDSA_VERIFY=y CONFIG_TPM=y CONFIG_ERRNO_STR=y CONFIG_GETOPT=y +CONFIG_ARGON2=y CONFIG_TEST_FDTDEC=y CONFIG_UNIT_TEST=y CONFIG_UT_TIME=y diff --git a/drivers/block/Makefile b/drivers/block/Makefile index b428a5cfb78..538b602790d 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -11,7 +11,7 @@ endif ifndef CONFIG_XPL_BUILD obj-$(CONFIG_IDE) += ide.o -obj-$(CONFIG_BLK_LUKS) += luks.o +obj-$(CONFIG_BLK_LUKS) += luks.o luks2.o obj-$(CONFIG_RKMTD) += rkmtd.o endif obj-$(CONFIG_SANDBOX) += sandbox.o host-uclass.o host_dev.o diff --git a/drivers/block/luks.c b/drivers/block/luks.c index 05df5b12c53..3bdfd7dba61 100644 --- a/drivers/block/luks.c +++ b/drivers/block/luks.c @@ -8,6 +8,7 @@ #include <blk.h> #include <blkmap.h> #include <dm.h> +#include <dm/ofnode.h> #include <hash.h> #include <hexdump.h> #include <json.h> @@ -21,6 +22,8 @@ #include <linux/err.h> #include <linux/errno.h> #include <linux/string.h> +#include <mbedtls/aes.h> +#include <mbedtls/cipher.h> #include <mbedtls/md.h> #include <mbedtls/pkcs5.h> #include <u-boot/sha256.h> @@ -476,6 +479,9 @@ int luks_unlock(struct udevice *blk, struct disk_partition *pinfo, } version = be16_to_cpu(*(__be16 *)(buffer + LUKS_MAGIC_LEN)); + if (version == LUKS_VERSION_2) + return unlock_luks2(blk, pinfo, pass, master_key, key_size); + if (version != LUKS_VERSION_1) { log_debug("unsupported LUKS version %d\n", version); return -ENOTSUPP; @@ -591,11 +597,12 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, { u8 essiv_key[SHA256_SUM_LEN]; /* SHA-256 output */ struct luks1_phdr *hdr; + struct luks2_hdr *hdr2; struct blk_desc *desc; struct udevice *dev; uint payload_offset; + int ret, version; bool use_essiv; - int ret; if (!blk || !pinfo || !master_key || !label || !blkmapp) return -EINVAL; @@ -608,7 +615,107 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, log_debug("failed to read LUKS header\n"); return -EIO; } - hdr = (struct luks1_phdr *)buf; + + /* Check version */ + version = be16_to_cpu(*(__be16 *)(buf + LUKS_MAGIC_LEN)); + + if (version == LUKS_VERSION_2) { + /* LUKS2: Parse JSON for segment offset */ + char *json_data; + u64 hdr_size, segment_offset; + int blocks; + struct abuf fdt_buf; + oftree tree; + ofnode root, segments_node, segment0_node; + const char *offset_str, *encryption; + + abuf_init(&fdt_buf); + + hdr2 = (struct luks2_hdr *)buf; + hdr_size = be64_to_cpu(hdr2->hdr_size); + + /* Read full header with JSON */ + blocks = (hdr_size + desc->blksz - 1) / desc->blksz; + json_data = malloc_cache_aligned(blocks * desc->blksz); + if (!json_data) + return -ENOMEM; + + if (blk_read(blk, pinfo->start, blocks, json_data) != blocks) { + free(json_data); + return -EIO; + } + + /* Convert JSON to FDT */ + ret = json_to_fdt(json_data + 4096, &fdt_buf); + if (ret) { + log_err("Failed to convert JSON to FDT: %d\n", ret); + free(json_data); + return -EINVAL; + } + + /* Create oftree from FDT */ + tree = oftree_from_fdt(abuf_data(&fdt_buf)); + if (!oftree_valid(tree)) { + abuf_uninit(&fdt_buf); + free(json_data); + return -EINVAL; + } + + /* Get root node */ + root = oftree_root(tree); + if (!ofnode_valid(root)) { + abuf_uninit(&fdt_buf); + free(json_data); + return -EINVAL; + } + + /* Navigate to segments node */ + segments_node = ofnode_find_subnode(root, "segments"); + if (!ofnode_valid(segments_node)) { + abuf_uninit(&fdt_buf); + free(json_data); + return -EINVAL; + } + + /* Get first segment (segment 0) */ + segment0_node = ofnode_find_subnode(segments_node, "0"); + if (!ofnode_valid(segment0_node)) { + abuf_uninit(&fdt_buf); + free(json_data); + return -EINVAL; + } + + /* Get offset (string in LUKS2 JSON) */ + offset_str = ofnode_read_string(segment0_node, "offset"); + if (!offset_str) { + abuf_uninit(&fdt_buf); + free(json_data); + return -EINVAL; + } + segment_offset = simple_strtoull(offset_str, NULL, 10); + + /* Convert byte offset to sectors */ + payload_offset = segment_offset / desc->blksz; + + /* Check if ESSIV mode is used */ + encryption = ofnode_read_string(segment0_node, "encryption"); + if (encryption) + use_essiv = strstr(encryption, "essiv"); + else + use_essiv = false; + + abuf_uninit(&fdt_buf); + free(json_data); + } else { + /* LUKS1 */ + hdr = (struct luks1_phdr *)buf; + + /* Check if ESSIV mode is used */ + use_essiv = strstr(hdr->cipher_mode, "essiv"); + + /* Get payload offset */ + payload_offset = be32_to_cpu(hdr->payload_offset); + } /* Create blkmap device */ ret = blkmap_create(label, &dev); @@ -617,9 +724,7 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, return ret; } - /* Check if ESSIV mode is used */ - use_essiv = strstr(hdr->cipher_mode, "essiv"); - + /* Compute ESSIV key if needed */ if (use_essiv) { int hash_size = SHA256_SUM_LEN; @@ -632,7 +737,6 @@ int luks_create_blkmap(struct udevice *blk, struct disk_partition *pinfo, } /* Map the encrypted partition to the blkmap device */ - payload_offset = be32_to_cpu(hdr->payload_offset); log_debug("mapping blkmap: blknr 0 blkcnt %lx payload_offset %x essiv %d\n", (ulong)pinfo->size, payload_offset, use_essiv); ret = blkmap_map_crypt(dev, 0, pinfo->size, blk, pinfo->start, diff --git a/drivers/block/luks2.c b/drivers/block/luks2.c new file mode 100644 index 00000000000..4720f9d92ce --- /dev/null +++ b/drivers/block/luks2.c @@ -0,0 +1,974 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LUKS2 (Linux Unified Key Setup version 2) support + * + * Copyright (C) 2025 Canonical Ltd + */ + +/* #define LOG_DEBUG */ + +#include <abuf.h> +#include <blk.h> +#include <dm.h> +#include <dm/ofnode.h> +#include <hash.h> +#include <json.h> +#include <log.h> +#include <luks.h> +#include <memalign.h> +#include <part.h> +#include <uboot_aes.h> +#include <asm/unaligned.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <mbedtls/aes.h> +#include <mbedtls/base64.h> +#include <mbedtls/md.h> +#include <mbedtls/pkcs5.h> +#include <u-boot/sha256.h> +#include <argon2.h> +#include "luks_internal.h" + +/** + * enum luks2_kdf_type - LUKS2 KDF type + * + * @LUKS2_KDF_PBKDF2: PBKDF2 key derivation function + * @LUKS2_KDF_ARGON2I: Argon2i key derivation function + * @LUKS2_KDF_ARGON2ID: Argon2id key derivation function + */ +enum luks2_kdf_type { + LUKS2_KDF_PBKDF2, + LUKS2_KDF_ARGON2I, + LUKS2_KDF_ARGON2ID, +}; + +/** + * struct luks2_digest - LUKS2 digest information + * + * @type: Digest KDF type + * @hash: Hash algorithm name (e.g., "sha256") + * @iters: PBKDF2 iteration count (valid if type == LUKS2_KDF_PBKDF2) + * @time: Argon2 time cost parameter (valid if type == LUKS2_KDF_ARGON2*) + * @memory: Argon2 memory cost parameter in KB (type == LUKS2_KDF_ARGON2*) + * @cpus: Argon2 parallelism/lanes parameter (type == LUKS2_KDF_ARGON2*) + * @salt: Decoded salt value + * @salt_len: Actual length of decoded salt + * @digest: Decoded digest (master key verification value) + * @digest_len: Actual length of decoded digest + */ +struct luks2_digest { + enum luks2_kdf_type type; + const char *hash; + u32 iters; + u32 time; + u32 memory; + u32 cpus; + u8 salt[LUKS_SALTSIZE]; + int salt_len; + u8 digest[128]; + int digest_len; +}; + +/** + * struct luks2_kdf - LUKS2 keyslot KDF parameters + * @type: KDF type + * @salt: Decoded KDF salt + * @salt_len: Actual length of decoded salt + * @iters: PBKDF2 iteration count (valid if type == LUKS2_KDF_PBKDF2) + * @time: Argon2 time cost parameter (valid if type == LUKS2_KDF_ARGON2*) + * @memory: Argon2 memory cost parameter in KB (type == LUKS2_KDF_ARGON2*) + * @cpus: Argon2 parallelism/lanes parameter (type == LUKS2_KDF_ARGON2*) + */ +struct luks2_kdf { + enum luks2_kdf_type type; + u8 salt[LUKS_SALTSIZE]; + int salt_len; + u32 iters; + u32 time; + u32 memory; + u32 cpus; +}; + +/** + * struct luks2_area - LUKS2 keyslot encrypted area parameters + * @offset: Byte offset from partition start where key material is stored + * @size: Size of encrypted key material in bytes + * @encryption: Encryption mode string (e.g., "aes-xts-plain64") + * @key_size: Encryption key size in bytes (32 for AES-256, 64 for XTS-512) + */ +struct luks2_area { + u64 offset; + u64 size; + const char *encryption; + u32 key_size; +}; + +/** + * struct luks2_af - LUKS2 keyslot anti-forensic parameters + * @stripes: Number of anti-forensic stripes (typically 4000) + * @hash: Hash algorithm name for AF merge operation + */ +struct luks2_af { + u32 stripes; + const char *hash; +}; + +/** + * struct luks2_keyslot - LUKS2 keyslot information + * @type: Keyslot type (should be "luks2") + * @key_size: Size of the master key in bytes + * @kdf: Key derivation function parameters + * @af: Anti-forensic parameters + * @area: Encrypted key material area parameters + */ +struct luks2_keyslot { + const char *type; + u32 key_size; + struct luks2_kdf kdf; + struct luks2_af af; + struct luks2_area area; +}; + +/** + * str_to_kdf_type() - Convert KDF type string to enum + * + * @type_str: KDF type string ("pbkdf2", "argon2i", or "argon2id") + * Return: enum luks2_kdf_type value, or negative error code if unknown type + */ +static int str_to_kdf_type(const char *type_str) +{ + if (!type_str) + return -EINVAL; + + if (!strcmp(type_str, "pbkdf2")) + return LUKS2_KDF_PBKDF2; + if (!strcmp(type_str, "argon2i")) + return LUKS2_KDF_ARGON2I; + if (!strcmp(type_str, "argon2id")) + return LUKS2_KDF_ARGON2ID; + + return -ENOTSUPP; +} + +/* Base64 decode wrapper for LUKS2 */ +static int base64_decode(const char *in, u8 *out, int out_len) +{ + size_t olen; + int ret; + + ret = mbedtls_base64_decode(out, out_len, &olen, + (const unsigned char *)in, strlen(in)); + if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) + return -ENOSPC; + if (ret == MBEDTLS_ERR_BASE64_INVALID_CHARACTER) + return -EINVAL; + if (ret) + return -EINVAL; + + return olen; +} + +/** + * read_digest_info() - Read LUKS2 digest information from ofnode + * + * @digest_node: ofnode for the digest (e.g., digest "0") + * @digest: Pointer to digest structure to fill + * Return: 0 on success, -ve on error + */ +static int read_digest_info(ofnode digest_node, struct luks2_digest *digest) +{ + const char *salt_b64, *digest_b64; + + const char *type_str; + int ret; + + memset(digest, '\0', sizeof(*digest)); + + /* Read and convert digest type */ + type_str = ofnode_read_string(digest_node, "type"); + ret = str_to_kdf_type(type_str); + if (ret < 0) { + log_debug("LUKS2: unsupported digest type %s\n", type_str); + return ret; + } + digest->type = ret; + + /* Check if Argon2 is supported if needed */ + if ((digest->type == LUKS2_KDF_ARGON2I || + digest->type == LUKS2_KDF_ARGON2ID) && + !IS_ENABLED(CONFIG_ARGON2)) { + log_debug("LUKS2: Argon2 not supported\n"); + return -ENOTSUPP; + } + + /* Read hash algorithm */ + digest->hash = ofnode_read_string(digest_node, "hash"); + if (!digest->hash) + return -EINVAL; + + /* Read KDF-specific parameters */ + if (digest->type == LUKS2_KDF_PBKDF2) { + /* PBKDF2 */ + if (ofnode_read_u32(digest_node, "iterations", &digest->iters)) + return -EINVAL; + } else { + /* Argon2 */ + if (ofnode_read_u32(digest_node, "time", &digest->time) || + ofnode_read_u32(digest_node, "memory", &digest->memory) || + ofnode_read_u32(digest_node, "cpus", &digest->cpus)) + return -EINVAL; + } + + /* Read and decode salt */ + salt_b64 = ofnode_read_string(digest_node, "salt"); + if (!salt_b64) + return -EINVAL; + digest->salt_len = base64_decode(salt_b64, digest->salt, + sizeof(digest->salt)); + if (digest->salt_len <= 0) + return -EINVAL; + + /* Read and decode digest */ + digest_b64 = ofnode_read_string(digest_node, "digest"); + if (!digest_b64) + return -EINVAL; + digest->digest_len = base64_decode(digest_b64, digest->digest, + sizeof(digest->digest)); + if (digest->digest_len <= 0) + return -EINVAL; + + return 0; +} + +/** + * read_keyslot_info() - Read LUKS2 keyslot information from ofnode + * + * @keyslot_node: ofnode for the keyslot (e.g., keyslot "0") + * @keyslot: Pointer to keyslot structure to fill + * @hash_name: Hash name to use for AF (from digest) + * Return: 0 on success, -ve on error + */ +static int read_keyslot_info(ofnode keyslot_node, struct luks2_keyslot *keyslot, + const char *hash_name) +{ + const char *salt_b64, *offset_str, *size_str; + ofnode kdf_node, af_node, area_node; + int ret; + + memset(keyslot, '\0', sizeof(*keyslot)); + + /* Read keyslot type */ + keyslot->type = ofnode_read_string(keyslot_node, "type"); + if (!keyslot->type || strcmp(keyslot->type, "luks2")) + return -EINVAL; + + /* Read key size */ + if (ofnode_read_u32(keyslot_node, "key_size", &keyslot->key_size)) + return -EINVAL; + + /* Navigate to and read KDF node */ + kdf_node = ofnode_find_subnode(keyslot_node, "kdf"); + if (!ofnode_valid(kdf_node)) + return -EINVAL; + + offset_str = ofnode_read_string(kdf_node, "type"); + ret = str_to_kdf_type(offset_str); + if (ret < 0) { + log_debug("LUKS2: unsupported KDF type %s\n", offset_str); + return ret; + } + keyslot->kdf.type = ret; + + /* Check if Argon2 is supported if needed */ + if ((keyslot->kdf.type == LUKS2_KDF_ARGON2I || + keyslot->kdf.type == LUKS2_KDF_ARGON2ID) && + !IS_ENABLED(CONFIG_ARGON2)) { + log_debug("LUKS2: Argon2 not supported\n"); + return -ENOTSUPP; + } + + /* Read KDF salt */ + salt_b64 = ofnode_read_string(kdf_node, "salt"); + if (!salt_b64) + return -EINVAL; + keyslot->kdf.salt_len = base64_decode(salt_b64, keyslot->kdf.salt, + sizeof(keyslot->kdf.salt)); + if (keyslot->kdf.salt_len <= 0) + return -EINVAL; + + /* Read KDF-specific parameters */ + if (keyslot->kdf.type == LUKS2_KDF_PBKDF2) { + if (ofnode_read_u32(kdf_node, "iterations", &keyslot->kdf.iters)) + return -EINVAL; + } else { + /* Argon2 */ + if (ofnode_read_u32(kdf_node, "time", &keyslot->kdf.time) || + ofnode_read_u32(kdf_node, "memory", &keyslot->kdf.memory) || + ofnode_read_u32(kdf_node, "cpus", &keyslot->kdf.cpus)) + return -EINVAL; + } + + /* Navigate to and read AF node */ + af_node = ofnode_find_subnode(keyslot_node, "af"); + if (!ofnode_valid(af_node)) + return -EINVAL; + + if (ofnode_read_u32(af_node, "stripes", &keyslot->af.stripes)) + keyslot->af.stripes = 4000; /* Default */ + keyslot->af.hash = hash_name; + + /* Navigate to and read area node */ + area_node = ofnode_find_subnode(keyslot_node, "area"); + if (!ofnode_valid(area_node)) + return -EINVAL; + + /* Read offset and size (strings in LUKS2 JSON) */ + offset_str = ofnode_read_string(area_node, "offset"); + if (!offset_str) + return -EINVAL; + keyslot->area.offset = simple_strtoull(offset_str, NULL, 10); + + size_str = ofnode_read_string(area_node, "size"); + if (!size_str) + return -EINVAL; + keyslot->area.size = simple_strtoull(size_str, NULL, 10); + + /* Read encryption mode */ + keyslot->area.encryption = ofnode_read_string(area_node, "encryption"); + if (!keyslot->area.encryption) + return -EINVAL; + + /* Read area key size */ + if (ofnode_read_u32(area_node, "key_size", &keyslot->area.key_size)) + return -EINVAL; + + return 0; +} + +/** + * read_luks2_info() - Read and parse LUKS2 header and metadata + * + * @blk: Block device + * @pinfo: Partition information + * @fdt_buf: Buffer to hold the converted FDT (caller must uninit) + * @digest: Pointer to digest structure to fill + * @md_type: Pointer to receive mbedtls MD type + * @keyslots_node: Pointer to receive keyslots ofnode + * Return: 0 on success, -ve on error + */ +static int read_luks2_info(struct udevice *blk, struct disk_partition *pinfo, + struct abuf *fdt_buf, struct luks2_digest *digest, + mbedtls_md_type_t *md_typep, ofnode *keyslots_nodep) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + ALLOC_CACHE_ALIGN_BUFFER(u8, buffer, desc->blksz); + ofnode root, digests_node, digest0; + struct hash_algo *hash_algo; + mbedtls_md_type_t md_type; + struct luks2_hdr *hdr; + ofnode keyslots_node; + char *json_data; + int count, ret; + u64 hdr_size; + oftree tree; + + abuf_init(fdt_buf); + + /* Read LUKS2 header */ + if (blk_read(blk, pinfo->start, 1, buffer) != 1) + return -EIO; + + hdr = (struct luks2_hdr *)buffer; + hdr_size = be64_to_cpu(hdr->hdr_size); + + log_debug("LUKS2: header size %llu bytes\n", hdr_size); + + /* Allocate and read full header with JSON */ + count = (hdr_size + desc->blksz - 1) / desc->blksz; + json_data = malloc_cache_aligned(count * desc->blksz); + if (!json_data) + return -ENOMEM; + + if (blk_read(blk, pinfo->start, count, json_data) != count) { + ret = -EIO; + goto out; + } + + ret = -EINVAL; + + /* JSON starts after a 4K binary header: convert to FDT */ + if (json_to_fdt(json_data + 4096, fdt_buf)) { + log_err("Failed to convert JSON to FDT\n"); + goto out; + } + + /* Create oftree from FDT */ + tree = oftree_from_fdt(abuf_data(fdt_buf)); + if (!oftree_valid(tree)) + goto out; + + /* Get root node */ + root = oftree_root(tree); + if (!ofnode_valid(root)) + goto out; + + /* Navigate to digests node and get digest 0 */ + digests_node = ofnode_find_subnode(root, "digests"); + if (!ofnode_valid(digests_node)) + goto out; + + digest0 = ofnode_find_subnode(digests_node, "0"); + if (!ofnode_valid(digest0)) + goto out; + + /* Read digest information */ + ret = read_digest_info(digest0, digest); + if (ret) + goto out; + + /* Get hash algorithm */ + ret = hash_lookup_algo(digest->hash, &hash_algo); + if (ret) { + log_debug("Unsupported hash: %s\n", digest->hash); + ret = -ENOTSUPP; + goto out; + } + md_type = hash_mbedtls_type(hash_algo); + + /* Navigate to keyslots node */ + keyslots_node = ofnode_find_subnode(root, "keyslots"); + if (!ofnode_valid(keyslots_node)) { + ret = -EINVAL; + goto out; + } + + *md_typep = md_type; + *keyslots_nodep = keyslots_node; + +out: + memset(json_data, '\0', count * desc->blksz); + free(json_data); + if (ret) + abuf_uninit(fdt_buf); + + return ret; +} + +/** + * essiv_decrypt() - Decrypt key material using ESSIV mode + * + * ESSIV (Encrypted Salt-Sector Initialization Vector) mode generates a unique + * IV for each sector by encrypting the sector number with a key derived from + * hashing the encryption key. + * + * @derived_key: Key derived from passphrase + * @key_size: Size of the encryption key in bytes + * @expkey: Expanded AES key for decryption + * @km: Encrypted key material buffer + * @split_key: Output buffer for decrypted key material + * @km_blocks: Number of blocks of key material + * @blksz: Block size in bytes + */ +static void essiv_decrypt(u8 *derived_key, uint key_size, u8 *expkey, + u8 *km, u8 *split_key, uint km_blocks, uint blksz) +{ + u8 essiv_expkey[AES256_EXPAND_KEY_LENGTH]; + u8 essiv_key_material[SHA256_SUM_LEN]; + u32 num_sectors = km_blocks; + u8 iv[AES_BLOCK_LENGTH]; + uint rel_sect; + + /* Generate ESSIV key by hashing the encryption key */ + log_debug("using ESSIV mode\n"); + sha256_csum_wd(derived_key, key_size, essiv_key_material, + CHUNKSZ_SHA256); + + log_debug_hex("ESSIV key[0-7]:", essiv_key_material, 8); + + /* Expand ESSIV key for AES */ + aes_expand_key(essiv_key_material, 256, essiv_expkey); + + /* + * Decrypt each sector with its own IV + * NOTE: sector number is relative to the key material buffer, + * not an absolute disk sector + */ + for (rel_sect = 0; rel_sect < num_sectors; rel_sect++) { + u8 sector_iv[AES_BLOCK_LENGTH]; + + /* Create IV: little-endian sector number padded to 16 bytes */ + memset(sector_iv, '\0', AES_BLOCK_LENGTH); + put_unaligned_le32(rel_sect, sector_iv); + + /* Encrypt sector number with ESSIV key to get IV */ + aes_encrypt(256, sector_iv, essiv_expkey, iv); + + /* Show the first sector for debugging */ + if (!rel_sect) { + log_debug("rel_sect %x, ", rel_sect); + log_debug_hex("IV[0-7]:", iv, 8); + } + + /* Decrypt this sector */ + aes_cbc_decrypt_blocks(key_size * 8, expkey, iv, + km + (rel_sect * blksz), + split_key + (rel_sect * blksz), + blksz / AES_BLOCK_LENGTH); + } +} + +/** + * decrypt_km_xts() - Decrypt key material using XTS mode + * + * Decrypts LUKS2 keyslot key material encrypted with AES-XTS mode. + * XTS mode uses 512-byte sectors with sector numbers as tweaks. + * + * @derived_key: Key derived from passphrase using KDF + * @key_size: Size of the derived key in bytes (32 or 64 for XTS) + * @km: Encrypted key material buffer + * @split_key: Output buffer for decrypted split key + * @size: Size of the split key in bytes + * Return: 0 on success, negative error code on failure + */ +static int decrypt_km_xts(const u8 *derived_key, uint key_size, const u8 *km, + u8 *split_key, int size) +{ + mbedtls_aes_xts_context ctx; + const int blksize = 512; + u8 data_unit[16]; + u64 sector; + int ret; + + /* Verify key size is valid for XTS (32 or 64 bytes) */ + if (key_size != 32 && key_size != 64) { + log_err("Unsupported XTS key size: %u\n", key_size); + return -EINVAL; + } + + mbedtls_aes_xts_init(&ctx); + ret = mbedtls_aes_xts_setkey_dec(&ctx, derived_key, key_size * 8); + if (ret) { + log_err("Failed to set XTS key: %d\n", ret); + mbedtls_aes_xts_free(&ctx); + return -EINVAL; + } + + /* + * XTS uses data unit (sector) as tweak + * LUKS2 uses 512-byte sectors for keyslot area + * Sector number is relative to start of keyslot area (not absolute) + */ + sector = 0; + + /* + * Decrypt in chunks (XTS requires whole sectors) + * Each sector has its own data_unit/tweak value + */ + for (u64 pos = 0; pos < size; pos += blksize) { + uint todo; + + todo = (size - pos > blksize) ? blksize : (size - pos); + + /* Prepare data_unit (sector number in little-endian) */ + memset(data_unit, '\0', sizeof(data_unit)); + for (int i = 0; i < 8; i++) + data_unit[i] = (sector >> (i * 8)) & 0xFF; + + ret = mbedtls_aes_crypt_xts(&ctx, MBEDTLS_AES_DECRYPT, todo, + data_unit, km + pos, + split_key + pos); + if (ret) { + log_err("XTS decryption failed at sector %llu: %d\n", + sector, ret); + mbedtls_aes_xts_free(&ctx); + return -EINVAL; + } + sector++; + } + + mbedtls_aes_xts_free(&ctx); + + return 0; +} + +/** + * decrypt_km_cbc() - Decrypt key material using CBC mode + * + * Decrypts LUKS keyslot key material encrypted with AES-CBC mode. + * Supports both ESSIV mode and plain CBC with zero IV. + * + * @derived_key: Key derived from passphrase using KDF + * @key_size: Size of the derived key in bytes + * @encrypt: Encryption-specification string (may contain "essiv") + * @km: Encrypted key material buffer + * @split_key: Output buffer for decrypted split key + * @size: Size of the split key in bytes + * @km_blocks: Number of blocks in key material + * @blksz: Block size in bytes + * Return: 0 on success, negative error code on failure + */ +static int decrypt_km_cbc(u8 *derived_key, uint key_size, const char *encrypt, + u8 *km, u8 *split_key, int size, int km_blocks, + int blksz) +{ + u8 expkey[AES256_EXPAND_KEY_LENGTH]; + + aes_expand_key(derived_key, key_size * 8, expkey); + + /* Check if ESSIV mode is used */ + if (strstr(encrypt, "essiv")) { + essiv_decrypt(derived_key, key_size, expkey, km, split_key, + km_blocks, blksz); + } else { + /* Plain CBC with zero IV */ + u8 iv[AES_BLOCK_LENGTH]; + + memset(iv, '\0', sizeof(iv)); + aes_cbc_decrypt_blocks(key_size * 8, expkey, iv, km, split_key, + size / AES_BLOCK_LENGTH); + } + + return 0; +} + +/* LUKS2-specific: Unlock using PBKDF2 keyslot */ +/** + * try_keyslot_pbkdf2() - Try to decrypt a LUKS2 keyslot using PBKDF2 + * + * Attempts to decrypt a LUKS2 keyslot using the PBKDF2 key derivation function. + * This involves deriving a key from the passphrase, reading the encrypted key + * material from disk, decrypting it (using either XTS or CBC mode), and + * recovering the candidate key through anti-forensic splitting. + * + * @blk: Block device containing the LUKS2 volume + * @pinfo: Partition information for the LUKS2 volume + * @ks: Keyslot information including KDF parameters and encryption area + * @pass: User passphrase to try + * @md_type: mbedtls message digest type for PBKDF2 + * @cand_key: Output buffer for the recovered candidate key + * Return: 0 on success, negative error code on failure + */ +static int try_keyslot_pbkdf2(struct udevice *blk, struct disk_partition *pinfo, + const struct luks2_keyslot *ks, const char *pass, + mbedtls_md_type_t md_type, u8 *cand_key) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + int ret, km_blocks, size; + u8 derived_key[128]; + u8 *split_key, *km; + + log_debug("LUKS2: trying keyslot with %u iters\n", ks->kdf.iters); + + /* Derive key from passphrase */ + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, (const u8 *)pass, + strlen(pass), ks->kdf.salt, + ks->kdf.salt_len, ks->kdf.iters, + ks->area.key_size, derived_key); + if (ret) + return -EPROTO; + + size = ks->key_size * ks->af.stripes; + km_blocks = (size + desc->blksz - 1) / desc->blksz; + + /* Allocate buffers */ + split_key = malloc(size); + km = malloc_cache_aligned(km_blocks * desc->blksz); + if (!split_key || !km) { + ret = -ENOMEM; + goto out; + } + + /* Read encrypted key material */ + ret = blk_read(blk, pinfo->start + (ks->area.offset / desc->blksz), + km_blocks, km); + if (ret != km_blocks) { + ret = -EIO; + goto out; + } + + /* Decrypt key material */ + if (strstr(ks->area.encryption, "xts")) + ret = decrypt_km_xts(derived_key, ks->area.key_size, km, + split_key, size); + else + ret = decrypt_km_cbc(derived_key, ks->area.key_size, + ks->area.encryption, km, split_key, size, + km_blocks, desc->blksz); + + if (ret) + goto out; + + /* AF-merge to recover candidate key */ + ret = af_merge(split_key, cand_key, ks->key_size, ks->af.stripes, + ks->af.hash); + +out: + if (split_key) { + memset(split_key, '\0', size); + free(split_key); + } + if (km) { + memset(km, '\0', km_blocks * desc->blksz); + free(km); + } + memset(derived_key, '\0', sizeof(derived_key)); + + return ret; +} + +/* Unlock using Argon2 keyslot */ +static int try_keyslot_argon2(struct udevice *blk, struct disk_partition *pinfo, + const struct luks2_keyslot *ks, const char *pass, + u8 *cand_key) +{ + struct blk_desc *desc = dev_get_uclass_plat(blk); + int ret, km_blocks, size; + u8 derived_key[128]; + u8 *split_key, *km; + + log_debug("LUKS2: trying keyslot with Argon2id (t=%u, m=%u, p=%u)\n", + ks->kdf.time, ks->kdf.memory, ks->kdf.cpus); + + /* Derive key from passphrase using Argon2id */ + log_debug("LUKS2 Argon2: passphrase='%s', t=%u, m=%u, p=%u, saltlen=%d, keylen=%u\n", + pass, ks->kdf.time, ks->kdf.memory, ks->kdf.cpus, + ks->kdf.salt_len, ks->area.key_size); + ret = argon2id_hash_raw(ks->kdf.time, ks->kdf.memory, ks->kdf.cpus, + pass, strlen(pass), ks->kdf.salt, + ks->kdf.salt_len, derived_key, + ks->area.key_size); + if (ret) { + log_err("Argon2id failed: %s\n", argon2_error_message(ret)); + return -EPROTO; + } + log_debug("LUKS2 Argon2: key derivation succeeded\n"); + + size = ks->key_size * ks->af.stripes; + km_blocks = (size + desc->blksz - 1) / desc->blksz; + + /* Allocate buffers */ + split_key = malloc(size); + km = malloc_cache_aligned(km_blocks * desc->blksz); + if (!split_key || !km) { + ret = -ENOMEM; + goto out; + } + + /* Read encrypted key material */ + ret = blk_read(blk, pinfo->start + (ks->area.offset / desc->blksz), + km_blocks, km); + if (ret != km_blocks) { + ret = -EIO; + goto out; + } + + log_debug("LUKS2 Argon2: read %d blocks from offset %llu, encryption=%s\n", + km_blocks, ks->area.offset, ks->area.encryption); + + /* Decrypt key material */ + if (strstr(ks->area.encryption, "xts")) + ret = decrypt_km_xts(derived_key, ks->area.key_size, + km, split_key, size); + else + ret = decrypt_km_cbc(derived_key, ks->area.key_size, + ks->area.encryption, km, split_key, + size, km_blocks, desc->blksz); + + if (ret) + goto out; + log_debug("LUKS2 Argon2: decryption completed successfully\n"); + + /* AF-merge to recover candidate key */ + log_debug("LUKS2 Argon2: calling AF-merge with key_size=%u, stripes=%u, hash=%s\n", + ks->key_size, ks->af.stripes, ks->af.hash); + ret = af_merge(split_key, cand_key, ks->key_size, ks->af.stripes, + ks->af.hash); + log_debug("LUKS2 Argon2: AF-merge returned %d\n", ret); + +out: + if (split_key) { + memset(split_key, '\0', size); + free(split_key); + } + if (km) { + memset(km, '\0', km_blocks * desc->blksz); + free(km); + } + memset(derived_key, '\0', sizeof(derived_key)); + + return ret; +} + +/** + * verify_master_key() - Verify a candidate master key against the digest + * + * This function takes a candidate master key (successfully derived from a + * keyslot) and verifies it matches the stored digest using the appropriate KDF. + * + * @digest: Digest information (KDF type, parameters, expected digest value) + * @md_type: mbedtls message digest type (for PBKDF2) + * @cand_key: The candidate master key to verify + * @key_size: Size of the candidate key + * @master_key: Output buffer for verified master key + * @key_sizep: Output pointer for key size + * Return: 0 if verified and copied to master_key, -EACCES if mismatch, -ve on + * error + */ +static int verify_master_key(const struct luks2_digest *digest, + mbedtls_md_type_t md_type, + const u8 *cand_key, uint key_size, u8 *master_key, + uint *key_sizep) +{ + u8 calculated_digest[128]; + int ret; + + log_debug("LUKS2: keyslot unlock succeeded, verifying digest (type=%d)\n", + digest->type); + + /* Verify against digest using the appropriate KDF */ + if (digest->type == LUKS2_KDF_PBKDF2) { + /* PBKDF2 digest verification */ + log_debug("LUKS2: verifying with PBKDF2 (iters=%u, saltlen=%d, digestlen=%d)\n", + digest->iters, digest->salt_len, digest->digest_len); + ret = mbedtls_pkcs5_pbkdf2_hmac_ext(md_type, cand_key, + key_size, digest->salt, + digest->salt_len, + digest->iters, + digest->digest_len, + calculated_digest); + if (ret) { + log_debug("PBKDF2 digest hash failed: %d\n", ret); + return -EACCES; + } + } else { + /* Argon2 digest verification */ + log_debug("LUKS2: verifying with Argon2 (t=%u, m=%u, p=%u)\n", + digest->time, digest->memory, digest->cpus); + ret = argon2id_hash_raw(digest->time, digest->memory, + digest->cpus, cand_key, key_size, + digest->salt, digest->salt_len, + calculated_digest, digest->digest_len); + if (ret) { + log_debug("Argon2 digest hash failed: %s\n", + argon2_error_message(ret)); + return -EACCES; + } + } + + log_debug("LUKS2: digest calculated, comparing...\n"); + if (memcmp(calculated_digest, digest->digest, digest->digest_len)) { + log_debug("LUKS2: digest mismatch!\n"); + return -EACCES; + } + + log_debug("LUKS2: digest match, unlock successful\n"); + memcpy(master_key, cand_key, key_size); + *key_sizep = key_size; + + return 0; /* Success! */ +} + +/** + * try_unlock_keyslot() - Try to unlock a single keyslot and verify master key + * + * This function attempts to unlock one keyslot by: + * 1. Reading keyslot metadata from ofnode + * 2. Deriving the candidate master key using the appropriate KDF + * 3. Verifying the candidate key against the stored digest + * + * @blk: Block device containing the LUKS partition + * @pinfo: Partition information + * @keyslot_node: ofnode for this specific keyslot + * @digest: Digest information for verification + * @md_type: mbedtls message digest type (for PBKDF2) + * @pass: User-provided passphrase + * @master_key: Output buffer for verified master key + * @key_sizep: Returns the key size + * Return: 0 if unlocked successfully, -EAGAIN to continue trying, -ve on error + */ +static int try_unlock_keyslot(struct udevice *blk, struct disk_partition *pinfo, + ofnode keyslot_node, + const struct luks2_digest *digest, + mbedtls_md_type_t md_type, const char *pass, + u8 *master_key, uint *key_sizep) +{ + struct luks2_keyslot keyslot; + u8 cand_key[128]; + uint key_size; + int ret; + + /* Read keyslot information */ + ret = read_keyslot_info(keyslot_node, &keyslot, digest->hash); + if (ret) { + /* Skip unsupported or invalid keyslots */ + return -EAGAIN; + } + + log_debug("LUKS2: trying keyslot (type=%d)\n", keyslot.kdf.type); + + /* Try the keyslot using the appropriate KDF */ + if (keyslot.kdf.type == LUKS2_KDF_PBKDF2) { + log_debug("LUKS2: calling try_keyslot_pbkdf2\n"); + ret = try_keyslot_pbkdf2(blk, pinfo, &keyslot, pass, md_type, + cand_key); + } else { + /* Argon2 (already checked for CONFIG_ARGON2 support) */ + log_debug("LUKS2: calling try_keyslot_argon2\n"); + ret = try_keyslot_argon2(blk, pinfo, &keyslot, pass, cand_key); + } + log_debug("LUKS2: keyslot try returned %d\n", ret); + + if (!ret) { + /* Verify the candidate key against the digest */ + ret = verify_master_key(digest, md_type, cand_key, + keyslot.key_size, master_key, + &key_size); + memset(cand_key, '\0', sizeof(cand_key)); + if (!ret) { + *key_sizep = key_size; + return 0; /* Success! */ + } + /* Verification failed, continue trying */ + } + + memset(cand_key, '\0', sizeof(cand_key)); + + return -EAGAIN; /* Continue trying other keyslots */ +} + +int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, + const char *pass, u8 *master_key, uint *key_sizep) +{ + ofnode keyslots_node, keyslot_node; + struct luks2_digest digest; + mbedtls_md_type_t md_type; + struct abuf fdt_buf; + int ret; + + /* Read and parse LUKS2 header and metadata */ + ret = read_luks2_info(blk, pinfo, &fdt_buf, &digest, &md_type, + &keyslots_node); + if (ret) + return ret; + + /* Try each keyslot until one succeeds */ + ret = -EACCES; + ofnode_for_each_subnode(keyslot_node, keyslots_node) { + ret = try_unlock_keyslot(blk, pinfo, keyslot_node, &digest, + md_type, pass, master_key, key_sizep); + if (!ret) /* Successfully unlocked! */ + break; + + /* -EAGAIN means skip, other errors also continue trying */ + } + abuf_uninit(&fdt_buf); + if (ret) { + if (ret == -EAGAIN) /* no usable slots */ + log_debug("LUKS2: no supported keyslots found\n"); + else /* no slots worked */ + log_debug("LUKS2: wrong passphrase\n"); + ret = -EACCES; + } + + return ret; +} diff --git a/drivers/block/luks_internal.h b/drivers/block/luks_internal.h index 32714787550..14d3839fe6a 100644 --- a/drivers/block/luks_internal.h +++ b/drivers/block/luks_internal.h @@ -27,4 +27,17 @@ int af_merge(const u8 *src, u8 *dst, size_t key_size, uint stripes, const char *hash_spec); +/** + * unlock_luks2() - Unlock a LUKS2 partition with a passphrase + * + * @blk: Block device + * @pinfo: Partition information + * @pass: Passphrase to unlock the partition + * @master_key: Buffer to receive the decrypted master key + * @key_sizep: Returns the key size + * Return: 0 on success, -ve on error + */ +int unlock_luks2(struct udevice *blk, struct disk_partition *pinfo, + const char *pass, u8 *master_key, uint *key_sizep); + #endif /* __LUKS_INTERNAL_H__ */ -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Allow unlocking a v2 LUKS partition. Signed-off-by: Simon Glass <simon.glass@canonical.com> --- cmd/luks.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/luks.c b/cmd/luks.c index 19f909be96b..c1e8035e685 100644 --- a/cmd/luks.c +++ b/cmd/luks.c @@ -85,10 +85,7 @@ static int do_luks_unlock(struct cmd_tbl *cmdtp, int flag, int argc, return CMD_RET_FAILURE; } - if (version != LUKS_VERSION_1) { - printf("Only LUKS1 is currently supported\n"); - return CMD_RET_FAILURE; - } + printf("Unlocking LUKS%d partition...\n", version); /* Unlock the partition to get the master key */ ret = luks_unlock(dev_desc->bdev, &info, passphrase, master_key, -- 2.43.0
From: Simon Glass <simon.glass@canonical.com> Add documentation for the new LUKSv2 feature and update LUKSv1 to mention the more common algorithm. Update the tests to use LUKSv2 for mmc12 Co-developed-by: Claude <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- doc/usage/cmd/luks.rst | 42 ++++-- doc/usage/luks.rst | 300 +++++++++++++++++++++++++++++++++++------ test/boot/luks.c | 29 ++++ 3 files changed, 318 insertions(+), 53 deletions(-) diff --git a/doc/usage/cmd/luks.rst b/doc/usage/cmd/luks.rst index 6c262e66200..3c8576dc8a1 100644 --- a/doc/usage/cmd/luks.rst +++ b/doc/usage/cmd/luks.rst @@ -57,7 +57,7 @@ For LUKS1 partitions, the following information is displayed: * Version number * Cipher name (encryption algorithm) -* Cipher mode (e.g., xts-plain64) +* Cipher mode (e.g., cbc-essiv:sha256) * Hash specification (e.g., sha256) * Payload offset (in sectors) * Key bytes (key size) @@ -88,24 +88,35 @@ dev[:part] luks unlock ~~~~~~~~~~~ -Unlock a LUKS1 encrypted partition using a passphrase. This command: +Unlock a LUKS encrypted partition using a passphrase. This command: -1. Verifies the partition is LUKS1 encrypted -2. Derives the encryption key using PBKDF2 with the provided passphrase -3. Attempts to unlock each active key slot -4. Verifies the master key against the stored digest -5. Creates a blkmap device providing on-the-fly decryption +1. Verifies the partition is LUKS encrypted (LUKS1 or LUKS2) +2. Parses LUKS2 JSON metadata (if LUKS2) using FDT conversion +3. Derives the encryption key using PBKDF2 or Argon2id with the provided + passphrase +4. Attempts to unlock each active key slot +5. Verifies the master key against the stored digest +6. Creates a blkmap device providing on-the-fly decryption After successful unlock, the decrypted data is accessible through a blkmap device (typically ``blkmap 0``). Standard U-Boot filesystem commands can then be used to access files on the unlocked partition. -**Currently only LUKS1 is supported for unlocking. LUKS2 unlock is not yet -implemented.** +**Supported LUKS versions:** -Supported cipher modes: +* LUKS1 (fully supported) +* LUKS2 (fully supported with PBKDF2 and Argon2id key derivation) -* aes-cbc-essiv:sha256 (AES in CBC mode with ESSIV) +**Supported cipher modes:** + +* **LUKS1**: aes-cbc-essiv:sha256 (AES in CBC mode with ESSIV) +* **LUKS2**: aes-cbc-essiv:sha256 and aes-xts-plain64 (AES-XTS, modern default) + +**Supported key derivation functions:** + +* **PBKDF2**: Traditional password-based key derivation (LUKS1 and LUKS2) +* **Argon2id**: Memory-hard KDF resistant to GPU attacks (LUKS2 only, requires + CONFIG_ARGON2) interface The storage interface type (e.g., mmc, usb, scsi) @@ -165,7 +176,7 @@ Display LUKS header information for a LUKS2 partition:: "0": { "type": "crypt", "offset": "16777216", - "encryption": "aes-xts-plain64", + "encryption": "aes-cbc-essiv:sha256", ... } }, @@ -232,9 +243,16 @@ For LUKS unlock functionality, additional options are required:: CONFIG_BLK_LUKS=y CONFIG_CMD_LUKS=y CONFIG_BLKMAP=y # For blkmap device support + CONFIG_JSON=y # For LUKS2 JSON metadata parsing (auto-selected by + # CONFIG_BLK_LUKS) CONFIG_AES=y # For AES encryption CONFIG_SHA256=y # For SHA-256 hashing CONFIG_PBKDF2=y # For PBKDF2 key derivation + CONFIG_MBEDTLS_LIB=y # For AES-XTS and mbedTLS crypto (LUKS2) + +For Argon2id support (modern LUKS2 KDF):: + + CONFIG_ARGON2=y # Argon2 password hashing (adds ~50KB to binary) Return value ------------ diff --git a/doc/usage/luks.rst b/doc/usage/luks.rst index 24be8d1ea81..db8558ffb56 100644 --- a/doc/usage/luks.rst +++ b/doc/usage/luks.rst @@ -1,14 +1,14 @@ .. SPDX-License-Identifier: GPL-2.0+ LUKS (Linux Unified Key Setup) -=============================== +============================== Overview -------- U-Boot provides support for accessing LUKS-encrypted partitions through the ``luks`` command and the blkmap device infrastructure. This allows U-Boot to -unlock and read data from LUKS1-encrypted block devices. +unlock and read data from LUKS1 and LUKS2-encrypted block devices. LUKS is a standard for disk encryption that stores encryption metadata in a header on the encrypted device. It supports multiple key slots, allowing @@ -17,19 +17,34 @@ different passphrases to unlock the same encrypted data. Currently, U-Boot supports: * LUKS version 1 (LUKS1) +* LUKS version 2 (LUKS2) with PBKDF2 and Argon2 key derivation * AES-256-CBC encryption with ESSIV (Encrypted Salt-Sector IV) mode -* Passphrase-based unlocking via PBKDF2 key derivation +* AES-256-XTS encryption (modern default for LUKS2) +* Passphrase-based unlocking via PBKDF2 or Argon2id key derivation * Reading ext4 and other filesystems from unlocked devices Supported Cipher Modes ------------------------ +---------------------- -The following LUKS1 cipher configurations are supported: +The following cipher configurations are supported: + +**LUKS1:** * **Cipher**: aes * **Mode**: cbc-essiv:sha256 * **Key sizes**: 128-bit, 192-bit, 256-bit -* **Hash**: sha256 (for PBKDF2) +* **KDF**: PBKDF2 with SHA256 + +**LUKS2:** + +* **Cipher**: aes +* **Mode**: cbc-essiv:sha256 or xts-plain64 +* **Key sizes**: 128-bit, 192-bit, 256-bit (CBC); 256-bit, 512-bit (XTS) +* **KDF**: PBKDF2 with SHA256, or Argon2id +* **Hash**: sha256, sha512 (for PBKDF2 and digests) + +XTS mode is the modern default for LUKS2 and provides better security properties +than CBC-ESSIV for disk encryption. Commands -------- @@ -39,11 +54,15 @@ See the :doc:`cmd/luks` for full details. luks detect ~~~~~~~~~~~ -Detect whether a partition is LUKS-encrypted:: +Detect whether a partition is LUKS-encrypted: + +:: luks detect <interface> <dev[:part]> -Example:: +Example: + +:: => luks detect mmc 0:2 LUKS1 encrypted partition detected @@ -53,11 +72,15 @@ This command checks for the LUKS magic bytes and validates the header structure. luks info ~~~~~~~~~ -Display information about a LUKS partition:: +Display information about a LUKS partition: + +:: luks info <interface> <dev[:part]> -Example:: +Example: + +:: => luks info mmc 0:2 Version: 1 @@ -73,11 +96,15 @@ offset to the encrypted data payload. luks unlock ~~~~~~~~~~~ -Unlock a LUKS partition with a passphrase:: +Unlock a LUKS partition with a passphrase: + +:: luks unlock <interface> <dev[:part]> <passphrase> -Example:: +Example: + +:: => luks unlock mmc 0:2 mypassword Trying to unlock LUKS partition... @@ -96,15 +123,17 @@ This command: 5. Creates a blkmap device that provides on-the-fly decryption Accessing Unlocked Data ------------------------- +----------------------- -After unlocking, the decrypted data is accessible through a blkmap device. -The device number is shown in the unlock output (typically ``blkmap 0``). +After unlocking, the decrypted data is accessible through a blkmap device. The +device number is shown in the unlock output (typically ``blkmap 0``). Listing Files ~~~~~~~~~~~~~ -Use standard filesystem commands to access the unlocked device:: +Use standard filesystem commands to access the unlocked device: + +:: => ls blkmap 0 / ./ @@ -120,15 +149,19 @@ Use standard filesystem commands to access the unlocked device:: Reading Files ~~~~~~~~~~~~~ -Read file contents:: +Read file contents: + +:: => cat blkmap 0 /hello.txt Hello from LUKS! Loading Files to Memory -~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~ + +Load files for further processing: -Load files for further processing:: +:: => ext4load blkmap 0 ${loadaddr} /boot/vmlinuz 5242880 bytes read in 123 ms (40.6 MiB/s) @@ -136,7 +169,9 @@ Load files for further processing:: Using with Boot Flows ~~~~~~~~~~~~~~~~~~~~~ -LUKS-encrypted partitions can be integrated into boot flows:: +LUKS-encrypted partitions can be integrated into boot flows: + +:: => bootflow scan => bootflow list @@ -149,16 +184,41 @@ Implementation Details Key Derivation ~~~~~~~~~~~~~~ -LUKS uses PBKDF2 (Password-Based Key Derivation Function 2) to derive -encryption keys from passphrases. The process: +LUKS supports two key derivation functions: + +**PBKDF2 (Password-Based Key Derivation Function 2)** + +Used by LUKS1 and LUKS2. The process: 1. Passphrase + salt → PBKDF2 → derived key 2. Derived key decrypts the AF-split key material 3. AF-merge combines the split key material → master key 4. Master key digest is verified against stored value +PBKDF2 applies HMAC-SHA256 iteratively (typically 100,000+ iterations) to make +brute-force attacks computationally expensive. + +**Argon2id (Modern KDF for LUKS2)** + +Argon2id is the winner of the Password Hashing Competition and provides better +resistance to GPU-based cracking attacks. The process: + +1. Passphrase + salt → Argon2id(time, memory, parallelism) → derived key +2. Derived key decrypts the AF-split key material +3. AF-merge combines the split key material → master key +4. Master key digest is verified against stored value + +Argon2id parameters: + +* **time**: Number of iterations through memory +* **memory**: Amount of memory used in KiB (e.g., 1048576 = 1GB) +* **parallelism**: Number of parallel threads/lanes + +Argon2id is memory-hard, making it resistant to ASIC and GPU attacks while +remaining fast on CPUs. + Anti-Forensic (AF) Splitter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~ LUKS uses an anti-forensic information splitter to protect key material: @@ -170,8 +230,8 @@ LUKS uses an anti-forensic information splitter to protect key material: ESSIV Mode ~~~~~~~~~~ -ESSIV (Encrypted Salt-Sector IV) generates unique initialization vectors -for each encrypted sector: +ESSIV (Encrypted Salt-Sector IV) generates unique initialization vectors for +each encrypted sector: 1. ESSIV key = SHA256(master_key) 2. For each sector: IV = AES_encrypt(sector_number, ESSIV_key) @@ -180,23 +240,86 @@ for each encrypted sector: This prevents watermarking attacks where identical plaintext blocks would produce identical ciphertext blocks. +XTS Mode +~~~~~~~~ + +XTS (XEX-based tweaked-codebook mode with ciphertext stealing) is the modern +standard for disk encryption and the default for LUKS2. It provides several +advantages over CBC-ESSIV: + +1. **Tweakable block cipher**: Each 512-byte sector uses the sector number as a + tweak +2. **No IV dependency**: Eliminates the need for ESSIV key derivation +3. **Parallel encryption**: Sectors can be encrypted/decrypted independently +4. **Better security**: Resistant to manipulation attacks on ciphertext + +XTS operation: + +1. Uses two AES keys (concatenated): 256-bit key → two 128-bit keys, or 512-bit + key → two 256-bit keys +2. For each 512-byte sector: + + * Data unit (tweak) = sector number in little-endian format + * Plaintext is encrypted using AES-XTS with the data unit + +3. Each 16-byte block within the sector uses a different tweak value derived + from the sector number + +XTS is specified in IEEE P1619 and is widely used in modern disk encryption. + +LUKS2 JSON Metadata Parsing +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LUKS2 stores its metadata in JSON format within the header. This is handled as +follows: + +1. **JSON to FDT Conversion**: The JSON metadata is converted to a Flattened + Device Tree (FDT) format using ``json_to_fdt()`` +2. **ofnode Navigation**: ofnode API is used to access the tree structure +3. **Type-Safe Property Reading**: Properties are read using + ``ofnode_read_string()`` and ``ofnode_read_u32()`` for type safety + +This approach provides several advantages: + +* **Robust Parsing**: Uses U-Boot's well-tested FDT/ofnode infrastructure +* **Type Safety**: Explicit type checking for all properties +* **Maintainability**: Clear code structure with proper node navigation +* **No String Searching**: Eliminates fragile string-based parsing + +Example navigation path for reading a keyslot's KDF type: + +:: + + root → keyslots → "0" → kdf → type + +The implementation properly handles LUKS2's mixed data types: + +* JSON numbers (iterations, time, memory) are stored as 32-bit integers +* JSON string-encoded numbers (offset, size) are parsed from strings +* JSON strings (type, hash, salt) are read directly + Blkmap Device Mapping ~~~~~~~~~~~~~~~~~~~~~ When a LUKS partition is unlocked, U-Boot creates a blkmap device that: * Maps to the underlying encrypted device -* Performs on-the-fly AES-CBC decryption with ESSIV +* Performs on-the-fly decryption (AES-CBC-ESSIV for LUKS1, AES-XTS for LUKS2) * Presents decrypted data as a standard block device * Supports whole-disk filesystems (no partition table required) -The blkmap device number can be used with any U-Boot command that accepts -block device specifications. +The blkmap device number can be used with any U-Boot command that accepts block +device specifications. Creating LUKS Partitions for Testing -------------------------------------- +------------------------------------ + +Using cryptsetup on Linux +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**LUKS1 with CBC-ESSIV (legacy):** -Using cryptsetup on Linux:: +:: # Create a 60MB file $ dd if=/dev/zero of=test.img bs=1M count=60 @@ -222,14 +345,71 @@ Using cryptsetup on Linux:: # Close the LUKS device $ cryptsetup close testluks +**LUKS2 with XTS and PBKDF2 (modern default):** + +:: + + # Create a 60MB file + $ dd if=/dev/zero of=test.img bs=1M count=60 + + # Format as LUKS2 with XTS + $ cryptsetup luksFormat --type luks2 \ + --cipher aes-xts-plain64 \ + --key-size 512 \ + --hash sha256 \ + --pbkdf pbkdf2 \ + test.img + + # Open, create filesystem, and add files (same as LUKS1) + $ cryptsetup open test.img testluks + $ mkfs.ext4 /dev/mapper/testluks + $ mount /dev/mapper/testluks /mnt + $ echo "Hello from LUKS2!" > /mnt/hello.txt + $ umount /mnt + $ cryptsetup close testluks + +**LUKS2 with XTS and Argon2id (maximum security):** + +:: + + # Create a 60MB file + $ dd if=/dev/zero of=test.img bs=1M count=60 + + # Format as LUKS2 with XTS and Argon2id + $ cryptsetup luksFormat --type luks2 \ + --cipher aes-xts-plain64 \ + --key-size 512 \ + --hash sha256 \ + --pbkdf argon2id \ + --pbkdf-memory 65536 \ + --pbkdf-parallel 4 \ + --iter-time 2000 \ + test.img + + # Open, create filesystem, and add files (same as above) + $ cryptsetup open test.img testluks + $ mkfs.ext4 /dev/mapper/testluks + $ mount /dev/mapper/testluks /mnt + $ echo "Hello from LUKS2 with Argon2!" > /mnt/hello.txt + $ umount /mnt + $ cryptsetup close testluks + +Note: Argon2 parameters: + +* ``--pbkdf-memory``: Memory in KiB (65536 = 64MB) +* ``--pbkdf-parallel``: Number of parallel threads +* ``--iter-time``: Target time in milliseconds for KDF + Using Python with U-Boot Test Infrastructure -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class: -See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class:: +:: from fs_helper import FsHelper, DiskHelper - # Create encrypted filesystem + # Create LUKS2 encrypted filesystem with PBKDF2 (default) with FsHelper(config, 'ext4', 30, 'test', part_mb=60, passphrase='mypassword') as fsh: @@ -240,32 +420,66 @@ See ``test/py/tests/fs_helper.py`` for the ``FsHelper`` class:: # Create encrypted filesystem fsh.mk_fs() + # Create LUKS2 encrypted filesystem with Argon2id + with FsHelper(config, 'ext4', 30, 'test', + part_mb=60, + encrypt_passphrase='mypassword', + luks_kdf='argon2id') as fsh: + # Add files to fsh.srcdir + with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: + f.write('Hello from LUKS with Argon2!\n') + + # Create encrypted filesystem + fsh.mk_fs() + + # Create LUKS1 encrypted filesystem + with FsHelper(config, 'ext4', 30, 'test', + part_mb=60, + encrypt_passphrase='mypassword', + luks_version=1) as fsh: + # Add files to fsh.srcdir + with open(os.path.join(fsh.srcdir, 'hello.txt'), 'w') as f: + f.write('Hello from LUKS1!\n') + + # Create encrypted filesystem + fsh.mk_fs() + Configuration Options --------------------- CONFIG_CMD_LUKS Enable the ``luks`` command +CONFIG_BLK_LUKS + Enable LUKS block device support + CONFIG_BLKMAP Enable blkmap device support (required for LUKS unlock) -CONFIG_AES - Enable AES encryption support +CONFIG_JSON + Enable JSON parsing support (automatically selected by CONFIG_BLK_LUKS for + LUKS2 metadata parsing) + +CONFIG_MBEDTLS_LIB + Enable mbedTLS library for AES-XTS and key derivation CONFIG_SHA256 Enable SHA-256 hashing support -CONFIG_PBKDF2 - Enable PBKDF2 key derivation +CONFIG_SHA512 + Enable SHA-512 hashing support (optional, for LUKS2) + +CONFIG_ARGON2 + Enable Argon2 key derivation support (optional, for modern LUKS2) Limitations ----------- -* Only LUKS1 is currently supported (LUKS2 not yet implemented) -* Only AES-CBC-ESSIV cipher mode is supported * Only passphrase-based unlocking is supported (no key files) * Unlocked devices are read-only (write support not yet implemented) * Master key remains in memory after unlocking (not securely erased on exit) +* Argon2 support requires CONFIG_ARGON2 to be enabled (adds ~50KB to binary) +* Large Argon2 memory requirements may not be suitable for all embedded systems Security Considerations ----------------------- @@ -302,7 +516,9 @@ Example Use Cases Encrypted Root Filesystem ~~~~~~~~~~~~~~~~~~~~~~~~~ -Boot from an encrypted root partition:: +Boot from an encrypted root partition: + +:: => luks unlock mmc 0:2 ${rootfs_password} => setenv bootargs root=/dev/blkmap0 rootfstype=ext4 @@ -313,7 +529,9 @@ Boot from an encrypted root partition:: Encrypted Configuration Storage ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Read configuration from encrypted partition:: +Read configuration from encrypted partition: + +:: => luks unlock mmc 0:3 ${config_password} => ext4load blkmap 0 ${loadaddr} /config/boot.conf diff --git a/test/boot/luks.c b/test/boot/luks.c index afa8c9f172e..4ee6081a790 100644 --- a/test/boot/luks.c +++ b/test/boot/luks.c @@ -229,6 +229,7 @@ static int bootstd_test_luks_unlock(struct unit_test_state *uts) /* Test unlocking partition 2 with correct passphrase */ ut_assertok(run_command("luks unlock mmc b:2 test", 0)); + ut_assert_nextline("Unlocking LUKS1 partition..."); ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-b:2'"); ut_assert_console_end(); @@ -239,3 +240,31 @@ static int bootstd_test_luks_unlock(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(bootstd_test_luks_unlock, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Test LUKS2 unlock command with LUKS2 encrypted partition */ +static int bootstd_test_luks2_unlock(struct unit_test_state *uts) +{ + struct udevice *mmc; + + ut_assertok(setup_mmc12(uts, &mmc)); + + /* Test that unlock command exists and handles errors properly */ + /* Should fail because partition 1 is not LUKS */ + ut_asserteq(1, run_command("luks unlock mmc c:1 test", 0)); + ut_assert_nextline("Not a LUKS partition"); + ut_assert_console_end(); + + /* Test unlocking partition 2 with correct passphrase */ + ut_assertok(run_command("luks unlock mmc c:2 test", 0)); + ut_assert_nextline("Unlocking LUKS2 partition..."); + ut_assert_nextline("Unlocked LUKS partition as blkmap device 'luks-mmc-c:2'"); + ut_assert_console_end(); + + /* Test unlocking with wrong passphrase */ + ut_asserteq(1, run_command("luks unlock mmc c:2 wrongpass", 0)); + ut_assert_nextline("Unlocking LUKS2 partition..."); + ut_assert_skip_to_line("Failed to unlock LUKS partition (err -13: Permission denied)"); + + return 0; +} +BOOTSTD_TEST(bootstd_test_luks2_unlock, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -- 2.43.0
On Tue, 2025-11-11 at 05:41 -0700, Simon Glass wrote:
From: Simon Glass <simon.glass@canonical.com>
Modern systems mostly use LUKSv2 as it is more secure that v1. This series provides an implementation of this feature, making use of the existing 'luks unlock' command.
Are you sure adding luks to u-boot is a good idea? I'm curious about your use case as seeing the patches brings up bad memories of grub for me :-) I thought the learning from that was to reduce duplicating code in the bootloader and leave fancy stuff like disk encryption and advanced file system features to Linux. IOW load signed but unencrypted kernel and initrd and handle the rest in Linux userspace. cu Ludwig -- (o_ Ludwig Nussel //\ Siemens AG / SI E R&D IOT V_/_ www.siemens.com
Hi Ludwig, On Wed, 12 Nov 2025 at 05:56, Nussel, Ludwig via Concept <concept@u-boot.org> wrote:
On Tue, 2025-11-11 at 05:41 -0700, Simon Glass wrote:
From: Simon Glass <simon.glass@canonical.com>
Modern systems mostly use LUKSv2 as it is more secure that v1. This series provides an implementation of this feature, making use of the existing 'luks unlock' command.
Are you sure adding luks to u-boot is a good idea? I'm curious about your use case as seeing the patches brings up bad memories of grub for me :-) I thought the learning from that was to reduce duplicating code in the bootloader and leave fancy stuff like disk encryption and advanced file system features to Linux. IOW load signed but unencrypted kernel and initrd and handle the rest in Linux userspace.
The use case is really just trying to avoid needing to start an initrd just to unlock the disk. It means that people select the OS and then (later) have to enter the key in a very different context. With the unlock in firmware we can start Linux without an initrd. We can also provide a unified UI, e.g. enter the unlock key directly in the boot menu. It isn't for everyone, but I believe it has value. For advanced filesystems, yes we should leave that to Linux. We have an ext4 boot partition with the OS, so that should be enough. I actually don't know much about the grub issue (more a user than a developer on that!) Can you give a few details? Regards, Simon
cu Ludwig
-- (o_ Ludwig Nussel //\ Siemens AG / SI E R&D IOT V_/_ www.siemens.com
_______________________________________________ Concept mailing list -- concept@u-boot.org To unsubscribe send an email to concept-leave@u-boot.org
On Wed, 2025-11-12 at 08:37 -0700, Simon Glass wrote:
On Wed, 12 Nov 2025 at 05:56, Nussel, Ludwig via Concept <concept@u-boot.org> wrote:
On Tue, 2025-11-11 at 05:41 -0700, Simon Glass wrote:
From: Simon Glass <simon.glass@canonical.com>
Modern systems mostly use LUKSv2 as it is more secure that v1. This series provides an implementation of this feature, making use of the existing 'luks unlock' command.
Are you sure adding luks to u-boot is a good idea? I'm curious about your use case as seeing the patches brings up bad memories of grub for me :-) I thought the learning from that was to reduce duplicating code in the bootloader and leave fancy stuff like disk encryption and advanced file system features to Linux. IOW load signed but unencrypted kernel and initrd and handle the rest in Linux userspace.
The use case is really just trying to avoid needing to start an initrd just to unlock the disk. It means that people select the OS and then (later) have to enter the key in a very different context. With the unlock in firmware we can start Linux without an initrd. We can also provide a unified UI, e.g. enter the unlock key directly in the boot menu.
Ideally Linux would boot so fast from the moment you press enter to the prompt of the initrd it shouldn't take much longer than the u-boot prompt itself ;) For such special use cases the initrd could be quite minimal and included in the kernel of course.
It isn't for everyone, but I believe it has value.
For advanced filesystems, yes we should leave that to Linux. We have an ext4 boot partition with the OS, so that should be enough.
I actually don't know much about the grub issue (more a user than a developer on that!) Can you give a few details?
When disk decryption was moved down to grub in openSUSE there were several issues that I am not sure are actually solved yet. For one it was dog slow. I guess the bootloader at least on PCs runs with limited resources, like not all CPUs and memory, probably even in real mode still for mbr boot. Grub was lagging behind with features. Even when Luks2 support arrived it lacked Argon2. So someone always has to keep up with changes on Linux side. Then one had to enter the passphrase twice. Once in grub and then again in the bootloader. To avoid that some key handover protocol had to be created. cu Ludwig -- (o_ Ludwig Nussel //\ Siemens AG / SI E R&D IOT V_/_ www.siemens.com
Hi Ludwig, On Wed, 12 Nov 2025 at 09:22, Nussel, Ludwig <ludwig.nussel@siemens.com> wrote:
On Wed, 2025-11-12 at 08:37 -0700, Simon Glass wrote:
On Wed, 12 Nov 2025 at 05:56, Nussel, Ludwig via Concept <concept@u-boot.org> wrote:
On Tue, 2025-11-11 at 05:41 -0700, Simon Glass wrote:
From: Simon Glass <simon.glass@canonical.com>
Modern systems mostly use LUKSv2 as it is more secure that v1. This series provides an implementation of this feature, making use of the existing 'luks unlock' command.
Are you sure adding luks to u-boot is a good idea? I'm curious about your use case as seeing the patches brings up bad memories of grub for me :-) I thought the learning from that was to reduce duplicating code in the bootloader and leave fancy stuff like disk encryption and advanced file system features to Linux. IOW load signed but unencrypted kernel and initrd and handle the rest in Linux userspace.
The use case is really just trying to avoid needing to start an initrd just to unlock the disk. It means that people select the OS and then (later) have to enter the key in a very different context. With the unlock in firmware we can start Linux without an initrd. We can also provide a unified UI, e.g. enter the unlock key directly in the boot menu.
Ideally Linux would boot so fast from the moment you press enter to the prompt of the initrd it shouldn't take much longer than the u-boot prompt itself ;) For such special use cases the initrd could be quite minimal and included in the kernel of course.
By the time you get to Linux you can't go back and try another OS if the key is wrong....other than rebooting. Linux does boot fairly quickly, but U-Boot can bring up a display in the hundreds of milliseconds...
It isn't for everyone, but I believe it has value.
For advanced filesystems, yes we should leave that to Linux. We have an ext4 boot partition with the OS, so that should be enough.
I actually don't know much about the grub issue (more a user than a developer on that!) Can you give a few details?
When disk decryption was moved down to grub in openSUSE there were several issues that I am not sure are actually solved yet. For one it was dog slow. I guess the bootloader at least on PCs runs with limited resources, like not all CPUs and memory, probably even in real mode still for mbr boot.
Yes, this is one of the challenges, IMO. The firmware environment is pretty primitive. We should be able to run at full speed. Not sure about multiple CPUs, though, and that might have some bearing on disk-unlock.
Grub was lagging behind with features. Even when Luks2 support arrived it lacked Argon2. So someone always has to keep up with changes on Linux side. Then one had to enter the passphrase twice. Once in grub and then again in the bootloader. To avoid that some key handover protocol had to be created.
Thanks for the info. From my limited view, it seems that Grub is not really suitable for the kind of active development that is needed to keep up with the world. As to entering it twice, I thought Grub was the bootloader? Do you mean that it needs to pass the key to Linux? Regards, Simon
On Wed, 2025-11-12 at 09:44 -0700, Simon Glass wrote:
[...] Ideally Linux would boot so fast from the moment you press enter to the prompt of the initrd it shouldn't take much longer than the u-boot prompt itself ;) For such special use cases the initrd could be quite minimal and included in the kernel of course.
By the time you get to Linux you can't go back and try another OS if the key is wrong....
Sure but then is this optimization really worth the effort? :-)
Grub was lagging behind with features. Even when Luks2 support arrived it lacked Argon2. So someone always has to keep up with changes on Linux side. Then one had to enter the passphrase twice. Once in grub and then again in the bootloader. To avoid that some key handover protocol had to be created.
Thanks for the info. From my limited view, it seems that Grub is not really suitable for the kind of active development that is needed to keep up with the world. As to entering it twice, I thought Grub was the bootloader? Do you mean that it needs to pass the key to Linux?
Hmm, yes? :-) Maybe we are talking about different setups. To clarify, I suppose the rootfs etc is inside an encrypted volume too. So even if it's the same volume as the one u-boot opened, Linux still needs set up device mapper with dmcrypt itself to be able to mount the rootfs, right? cu Ludwig -- (o_ Ludwig Nussel //\ Siemens AG / SI E R&D IOT V_/_ www.siemens.com
Hi Ludwig, On Thu, 13 Nov 2025 at 01:34, Nussel, Ludwig <ludwig.nussel@siemens.com> wrote:
On Wed, 2025-11-12 at 09:44 -0700, Simon Glass wrote:
[...] Ideally Linux would boot so fast from the moment you press enter to the prompt of the initrd it shouldn't take much longer than the u-boot prompt itself ;) For such special use cases the initrd could be quite minimal and included in the kernel of course.
By the time you get to Linux you can't go back and try another OS if the key is wrong....
Sure but then is this optimization really worth the effort? :-)
To me it is, as I try to build a cohesive picture of the boot process where we have full knowledge of what is going on, even in firmware. Today's solution feels like a patchwork of pieces that happen to fit together, and not necessarily that well.
Grub was lagging behind with features. Even when Luks2 support arrived it lacked Argon2. So someone always has to keep up with changes on Linux side. Then one had to enter the passphrase twice. Once in grub and then again in the bootloader. To avoid that some key handover protocol had to be created.
Thanks for the info. From my limited view, it seems that Grub is not really suitable for the kind of active development that is needed to keep up with the world. As to entering it twice, I thought Grub was the bootloader? Do you mean that it needs to pass the key to Linux?
Hmm, yes? :-) Maybe we are talking about different setups. To clarify, I suppose the rootfs etc is inside an encrypted volume too. So even if it's the same volume as the one u-boot opened, Linux still needs set up device mapper with dmcrypt itself to be able to mount the rootfs, right?
Right, if the rootfs is encrypted then all bets are off. I really don't think encrypting the boot partition has much value. It also makes it impossible for firmware to see what is booting. In fact I'm not even sure how this would work, unless you wanted to use EFI and put the kernel initrd in a FAT filesystem? Do you know the details on that? As I understand it the recommended way to install a distro these days is to have an ext4 /boot partition. Regards, Simon
participants (3)
-
Nussel, Ludwig -
Simon Glass -
Simon Glass