From e60342575a888dd13fc2df841a8d4c7ef683ae6e Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Wed, 23 Sep 2026 23:26:11 +0200 Subject: Introduce librecrypt_concat_*_ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- Makefile | 4 +++ argon2/make_settings.c | 49 +++++++++---------------- common.h | 89 ++++++++++++++++++++++++++++++++++++++++++++-- librecrypt.h | 2 +- librecrypt_add_algorithm.c | 56 +++++++++++++++++------------ librecrypt_concat_mem_.c | 25 +++++++++++++ librecrypt_concat_str_.c | 25 +++++++++++++ librecrypt_concat_uint_.c | 41 +++++++++++++++++++++ librecrypt_concat_void_.c | 25 +++++++++++++ librecrypt_hash_.c | 2 ++ librecrypt_realise_salts.c | 2 ++ 11 files changed, 262 insertions(+), 58 deletions(-) create mode 100644 librecrypt_concat_mem_.c create mode 100644 librecrypt_concat_str_.c create mode 100644 librecrypt_concat_uint_.c create mode 100644 librecrypt_concat_void_.c diff --git a/Makefile b/Makefile index 572b9ab..33e9c8a 100644 --- a/Makefile +++ b/Makefile @@ -68,6 +68,10 @@ OBJ_PRIVATE =\ librecrypt_fill_with_random_.o\ librecrypt_find_first_algorithm_.o\ librecrypt_get_pepper_.o\ + librecrypt_concat_mem_.o\ + librecrypt_concat_str_.o\ + librecrypt_concat_uint_.o\ + librecrypt_concat_void_.o\ $(OBJ_COMMON_RFC4848S4) USE_OBJ_COMMON_RFC4848S4 =\ diff --git a/argon2/make_settings.c b/argon2/make_settings.c index c6d2128..63062f3 100644 --- a/argon2/make_settings.c +++ b/argon2/make_settings.c @@ -7,9 +7,9 @@ static ssize_t make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost, int gensalt, ssize_t (*rng)(void *out, size_t n, void *user), void *user) { + struct concat_state concat_state = {out_buffer, size, 0u}; const char *p, *version; - size_t algolen, ret, min, len, i; - int r; + size_t algolen, min, len, i; /* Use default RNG if NULL is specified */ if (!rng) @@ -44,7 +44,7 @@ make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memco abort(); /* $covered$ */ p = strchr(p, '$'); algolen = p ? (size_t)(p - algorithm) : strlen(algorithm); - if (algolen > 32u) /* just some small value absolute will fit all variants */ + if (algolen > 32u) /* just some small value that absolutely will fit all variants */ abort(); /* $covered$ */ if (p && p[1u] == 'v') { p = &p[2u]; @@ -63,21 +63,23 @@ make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memco } /* Write algorithm and parameters */ - r = snprintf(out_buffer, size, "%.*s%s$m=%zu,t=%ju,p=1$", - (int)algolen, algorithm, version, memcost, timecost); - if (r < (int)sizeof("$argon2_$v=__$m=_,t=_,p=1$") - 1) + librecrypt_concat_mem_(&concat_state, algorithm, algolen); + librecrypt_concat_str_(&concat_state, version); + librecrypt_concat_str_(&concat_state, "$m="); + librecrypt_concat_uint_(&concat_state, memcost); + librecrypt_concat_str_(&concat_state, ",t="); + librecrypt_concat_uint_(&concat_state, timecost); + librecrypt_concat_str_(&concat_state, ",p=1$"); + if (concat_state.len < sizeof("$argon2_$v=__$m=_,t=_,p=1$") - 1u) abort(); /* $covered$ (impossible) */ - ret = (size_t)r; - min = size ? MIN(ret, size - 1u) : 0u; - out_buffer = &out_buffer[min]; - size -= min; /* Add 16 bytes of salt */ if (gensalt) { /* 16 bytes is 128 bits, and 128 = 21*6+2, so that is * 21 full base-64 characeters and 1 that only use 2 bits */ - ret += len = 22u; - min = size ? MIN(len, size - 1u) : 0u; + out_buffer = concat_state.buf; + len = 22u; + min = librecrypt_concat_void_(&concat_state, len); if (librecrypt_fill_with_random_(out_buffer, min, rng, user)) return -1; if (min == len) @@ -85,30 +87,13 @@ make_settings(char *out_buffer, size_t size, const char *algorithm, size_t memco for (i = 0u; i < min; i++) out_buffer[i] = librecrypt_common_rfc4848s4_encoding_lut_[((unsigned char *)out_buffer)[i]]; } else { - ret += len = sizeof("*16") - 1u; - min = size ? MIN(len, size - 1u) : 0u; - if (min) - memcpy(out_buffer, "*16", min); + librecrypt_concat_str_(&concat_state, "*16"); } - out_buffer = &out_buffer[min]; - size -= min; /* Add tag size (size of hash result) */ - ret += len = sizeof("$*32") - 1u; - min = size ? MIN(len, size - 1u) : 0u; - if (min) - memcpy(out_buffer, "$*32", min); - out_buffer = &out_buffer[min]; - size -= min; - - /* NUL terminate */ - if (size) { - /* We were careful to make sure size is positive at - * the end if it was when the function was called */ - *out_buffer = '\0'; - } + librecrypt_concat_str_(&concat_state, "$*32"); - return (ssize_t)ret; + return (ssize_t)concat_state.len; enosys: errno = ENOSYS; diff --git a/common.h b/common.h index bc04b44..fd53468 100644 --- a/common.h +++ b/common.h @@ -19,8 +19,8 @@ #include #include #include -#include #include +#include #if defined(__GNUC__) @@ -117,6 +117,50 @@ struct pepper { }; +/** + * Structure used for doing truncated text concatenation + * + * This structure is laid out so that `{buf, size, 0u}` will + * properly initialise the state to use a buffer and have + * it's size properly set, and have the resulting length + * initialised to zero. This will not change. + */ +struct concat_state { + /** + * Where the function shall write to + * + * Update to an offset of it by the called function. + * Shall be set to the beginning of the output buffer + * before the first function call. + */ + char *buf; + + /** + * The (remaining) size of the output buffer + * + * This is decremented by the called function + * Shall be set to the size of the output buffer + * before the first function call. + */ + size_t size; + + /** + * The length the constructed text would + * have had if it wasn't truncated + * + * This length does not include NUL termination + * which is added by every function + * + * This is increased by the called function. + * Should be set to 0 before the first function call. + * + * After the last concatenation call, this field + * is read to get the final size before truncation + */ + size_t len; +}; + + /** * The real type of `LIBRECRYPT_CONTEXT` */ @@ -330,10 +374,51 @@ const struct librecrypt_algorithm *librecrypt_find_first_algorithm_(const char * * @throws EINVAL The size of the pepper is unsupported * for the hash algorithm `algo` */ -LIBRECRYPT_NONNULL_1__ +LIBRECRYPT_NONNULL_1__ HIDDEN struct pepper *librecrypt_get_pepper_(LIBRECRYPT_CONTEXT *ctx, enum librecrypt_hash_algorithm algo, size_t len); +LIBRECRYPT_NONNULL_1__ HIDDEN +inline size_t +librecrypt_concat_void_(struct concat_state *state, size_t len) /* TODO doc */ +{ + size_t n = state->size ? MIN(state->size - 1u, len) : 0u; + if (len > SIZE_MAX - state->len) + abort(); /* TODO not covered */ + state->len += len; + if (n) { + state->buf = &state->buf[n]; + state->size -= n; + } + if (state->size) + state->buf[0u] = '\0'; + return n; +} + + +LIBRECRYPT_NONNULL_1__ LIBRECRYPT_READ_MEM__(2, 3) HIDDEN +inline void +librecrypt_concat_mem_(struct concat_state *state, const char *text, size_t len) /* TODO doc */ +{ + char *buf = state->buf; + size_t n = librecrypt_concat_void_(state, len); + if (n) + memcpy(buf, text, n); +} + + +LIBRECRYPT_NONNULL_1__ LIBRECRYPT_READ_STR__(2) HIDDEN +inline void +librecrypt_concat_str_(struct concat_state *state, const char *text) /* TODO doc */ +{ + librecrypt_concat_mem_(state, text, strlen(text)); +} + + +LIBRECRYPT_NONNULL_1__ HIDDEN +void librecrypt_concat_uint_(struct concat_state *state, uintmax_t value); /* TODO doc */ + + #ifdef TEST # include "libtest/libtest.h" diff --git a/librecrypt.h b/librecrypt.h index 11d6007..aec7574 100644 --- a/librecrypt.h +++ b/librecrypt.h @@ -442,7 +442,7 @@ struct librecrypt_algorithm { * @return See `librecrypt_make_settings` * @throws See `librecrypt_make_settings` * - * This function shall be MT-Safe but may be AS-Safe + * This function shall be MT-Safe but may be AS-Unsafe */ ssize_t (*make_settings)(char *out_buffer, size_t size, const char *algorithm, size_t memcost, uintmax_t timecost, int gensalt, diff --git a/librecrypt_add_algorithm.c b/librecrypt_add_algorithm.c index d561983..24a17c7 100644 --- a/librecrypt_add_algorithm.c +++ b/librecrypt_add_algorithm.c @@ -3,19 +3,35 @@ #ifndef TEST -#if defined(__GNUC__) -# pragma GCC diagnostic ignored "-Wformat-truncation=" /* we rely on snprintf(3) doing truncation */ -#endif +static size_t +truncated_asterisk(char *out_buffer, size_t out_buffer_size, size_t asterisk) +{ + struct concat_state concat_state = {out_buffer, out_buffer_size, 0u}; + librecrypt_concat_str_(&concat_state, "*"); + librecrypt_concat_uint_(&concat_state, asterisk); + if (concat_state.len < 2u) + abort(); /* $covered$ (impossible) */ + return concat_state.len; +} + + +static size_t +measure_asterisk(size_t asterisk) +{ + return truncated_asterisk(NULL, 0u, asterisk); +} ssize_t librecrypt_add_algorithm(char *out_buffer, size_t size, const char *augend, const char *restrict augment, LIBRECRYPT_CONTEXT *ctx) { + /* TODO rewrite to use the string building functions */ + size_t prefix1, prefix2, min, ret, len, phraselen; - size_t hashsize1, hashsize2; + size_t hashsize1, hashsize2, asterisk_len; char *phrase, pad; - int strict_pad, r_int, nul_term, saved_errno; + int strict_pad, nul_term, saved_errno; const unsigned char *lut; ssize_t r; @@ -60,12 +76,10 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, const char *augend, out_buffer = &out_buffer[min]; size -= min; if (hashsize2) { - r_int = snprintf(out_buffer, size + 1u, "*%zu", hashsize2); - if (r_int < 2) - abort(); /* $covered$ (impossible reliably) */ - if (ret > SIZE_MAX - (size_t)r_int) + asterisk_len = truncated_asterisk(out_buffer, size + 1u, hashsize2); + if (ret > SIZE_MAX - asterisk_len) abort(); /* $covered$ (impossible) */ - ret += (size_t)r_int; + ret += asterisk_len; } else { out_buffer[0u] = '\0'; } @@ -79,12 +93,10 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, const char *augend, #if defined(__GNUC__) # pragma GCC diagnostic pop #endif - r_int = snprintf(NULL, 0u, "*%zu", hashsize2); - if (r_int < 2) - abort(); /* $covered$ (impossible reliably) */ - if (ret > SIZE_MAX - (size_t)r_int) + asterisk_len = measure_asterisk(hashsize2); + if (ret > SIZE_MAX - asterisk_len) abort(); /* $covered$ (impossible) */ - ret += (size_t)r_int; + ret += asterisk_len; out: if (nul_term) out_buffer[0u] = '\0'; @@ -100,17 +112,15 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, const char *augend, /* Measure size of hash size specification for `augend` */ if (hashsize1) { - r_int = snprintf(NULL, 0u, "*%zu", hashsize1); - if (r_int < 2) - abort(); /* $covered$ (impossible reliably) */ + asterisk_len = measure_asterisk(hashsize1); } else { - r_int = 0; + asterisk_len = 0; } /* Measure `augend` and '>' in output */ - if (prefix1 > SIZE_MAX - 1u - (size_t)r_int) + if (prefix1 > SIZE_MAX - 1u - asterisk_len) abort(); /* $covered$ (impossible) */ - ret = prefix1 + (size_t)r_int + 1u; + ret = prefix1 + asterisk_len + 1u; /* Decode the hash from base-64 to binary */ if (size <= ret + prefix2) { @@ -151,9 +161,9 @@ librecrypt_add_algorithm(char *out_buffer, size_t size, const char *augend, out_buffer = &out_buffer[min]; size -= min; if (hashsize1 && size) { - if (snprintf(out_buffer, size + 1u, "*%zu", hashsize1) != r_int) + if (truncated_asterisk(out_buffer, size + 1u, hashsize1) != asterisk_len) abort(); /* $covered$ (impossible reliably) */ - min = MIN((size_t)r_int, size); + min = MIN(asterisk_len, size); out_buffer = &out_buffer[min]; size -= min; } diff --git a/librecrypt_concat_mem_.c b/librecrypt_concat_mem_.c new file mode 100644 index 0000000..edc826a --- /dev/null +++ b/librecrypt_concat_mem_.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline void librecrypt_concat_mem_(struct concat_state *state, const char *text, size_t len); + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + /* TODO test */ + + STOP_RESOURCE_TEST(); + return 0; +} + + +#endif diff --git a/librecrypt_concat_str_.c b/librecrypt_concat_str_.c new file mode 100644 index 0000000..6ad7e48 --- /dev/null +++ b/librecrypt_concat_str_.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline void librecrypt_concat_str_(struct concat_state *state, const char *text); + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + /* TODO test */ + + STOP_RESOURCE_TEST(); + return 0; +} + + +#endif diff --git a/librecrypt_concat_uint_.c b/librecrypt_concat_uint_.c new file mode 100644 index 0000000..a492e0b --- /dev/null +++ b/librecrypt_concat_uint_.c @@ -0,0 +1,41 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST +#include + +/* TODO use of sprintf(3) should be eliminated; + * this will make it easier to use in an unhosted environment (kernels) + * and make_settings will be AS-Safe, + * however librecrypt_add_algorithm will not be AS-Safe as hash + * functions may require memory allocations which is AS-Unsafe + */ + + +void +librecrypt_concat_uint_(struct concat_state *state, uintmax_t value) +{ + char buf[3u * sizeof(uintmax_t) + 1u]; + int len = sprintf(buf, "%ju", value); + if (len < 1) + abort(); /* $covered$ (impossible reliably) */ + librecrypt_concat_mem_(state, buf, (size_t)len); +} + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + /* TODO test */ + + STOP_RESOURCE_TEST(); + return 0; +} + + +#endif diff --git a/librecrypt_concat_void_.c b/librecrypt_concat_void_.c new file mode 100644 index 0000000..889aed1 --- /dev/null +++ b/librecrypt_concat_void_.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" +#ifndef TEST + + +extern inline size_t librecrypt_concat_void_(struct concat_state *state, size_t len); + + +#else + + +int +main(void) +{ + SET_UP_ALARM(); + INIT_RESOURCE_TEST(); + + /* TODO test */ + + STOP_RESOURCE_TEST(); + return 0; +} + + +#endif diff --git a/librecrypt_hash_.c b/librecrypt_hash_.c index 164a47b..fcaa795 100644 --- a/librecrypt_hash_.c +++ b/librecrypt_hash_.c @@ -46,6 +46,8 @@ ssize_t librecrypt_hash_(char *restrict out_buffer, size_t size, const char *phrase, size_t len, const char *settings, LIBRECRYPT_CONTEXT *ctx, enum action action) { + /* TODO rewrite to use the string building functions */ + const struct librecrypt_algorithm *algo; ssize_t (*rng)(void *out, size_t n, void *user) = NULL; char *settings_scratch = NULL; diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c index 0c8002a..256cf68 100644 --- a/librecrypt_realise_salts.c +++ b/librecrypt_realise_salts.c @@ -8,6 +8,8 @@ librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *set ssize_t (*rng)(void *out, size_t n, void *user), void *user, LIBRECRYPT_CONTEXT *ctx) { + /* TODO rewrite to use the string building functions */ + const char *lut; char pad; int strict_pad, nul_term = 0; -- cgit v1.3.1