From adfa8e1265f6155d1a582baa9929af198bb5d4de Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 1 May 2026 17:45:39 +0200 Subject: Misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- librecrypt_realise_salts.c | 154 ++++++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 66 deletions(-) (limited to 'librecrypt_realise_salts.c') diff --git a/librecrypt_realise_salts.c b/librecrypt_realise_salts.c index a4204a6..dada7f1 100644 --- a/librecrypt_realise_salts.c +++ b/librecrypt_realise_salts.c @@ -9,71 +9,58 @@ librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *set { const char *lut; char pad; - int strict_pad, has_asterisk, nul_term = 0; - size_t i, j, n, min, ret = 0u; + int strict_pad, nul_term = 0; + size_t i, min, nasterisks, prefix, ret = 0u; size_t count, digit, q, r, left, mid, right; + /* If we are doing output, it should be NUL-terminated */ if (size) { nul_term = 1; size -= 1u; } - for (;;) { - for (; *settings == LIBRECRYPT_ALGORITHM_LINK_DELIMITER; settings++) { - if (size) { - *out_buffer++ = LIBRECRYPT_ALGORITHM_LINK_DELIMITER; - size -= 1u; - } - ret += 1u; - } - - if (!*settings) - break; - - has_asterisk = 0; - for (n = 0u; settings[n] && settings[n] != LIBRECRYPT_ALGORITHM_LINK_DELIMITER; n++) - if (settings[n] == '*') - has_asterisk = 1; - - if (!has_asterisk) { - min = size < n ? size : n; - memcpy(out_buffer, settings, min); - out_buffer += min; - settings = &settings[n]; - ret += n; - continue; - } - - if (*settings == '*') { - errno = EINVAL; - return -1; + /* For each chained algorithm */ + while (*settings) { + /* Get the number of '*' that are not in the result + * (hash size specification), and also length of + * the algorithm configuration, so we can identify + * the algorithm next and get its binary data encoding + * format (just calling librecrypt_get_encoding on the + * entire string would get it for the last algorithm + * in the chain) */ + nasterisks = 0u; + count = 0u; + for (prefix = 0u; settings[prefix]; prefix++) { + if (settings[prefix] == LIBRECRYPT_HASH_COMPOSITION_DELIMITER) + nasterisks = count; + else if (settings[prefix] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + break; + else if (settings[prefix] == '*') + count += 1u; } - lut = librecrypt_get_encoding(settings, n, &pad, &strict_pad, 0); + /* Get binary data encoding format */ + lut = librecrypt_get_encoding(settings, prefix, &pad, &strict_pad, 0); if (!lut) return -1; pad = strict_pad ? pad : '\0'; - for (i = 0; i < n;) { - if (settings[i] != '*') { - if (size) { - *out_buffer++ = settings[i]; - size -= 1u; - } - ret += 1u; - i++; - continue; - } + /* Process each asterisk-encoded salt */ + while (nasterisks--) { + /* Copy text before next '*' */ + for (i = 0u; settings[i] != '*'; i++); + min = i < size ? i : size; + memcpy(out_buffer, settings, min); + size -= min; + settings = &settings[i]; + ret += i; - if (++i == n) { - if (size) { - *out_buffer++ = '*'; - size -= 1u; - } - ret += 1u; - break; - } - if ('0' > settings[i] || settings[i] > '9') { + /* Skip past the '*' */ + settings++; + + /* If the '*' is not followed by an unsigned, + * decimal integer include it literally */ + if ('0' > settings[1u] || settings[1u] > '9') { if (size) { *out_buffer++ = '*'; size -= 1u; @@ -82,49 +69,84 @@ librecrypt_realise_salts(char *restrict out_buffer, size_t size, const char *set continue; } - count = 0; - for (; i < n && '0' <= settings[i] && settings[i] <= '9'; i++) { - digit = (size_t)(settings[i] - '0'); + /* Parse encoded integer */ + count = 0u; + while ('0' <= *settings && *settings <= '9') { + digit = (size_t)(*settings++ - '0'); if (count > ((size_t)SSIZE_MAX - digit) / 10u) goto erange; count *= 10u; count += digit; } + /* Get number of random base-64 characters to generated, + * and the number of padding characters to append */ + /* 1 byte (8 bits) requires 2 base-64 characters (12 bits, 4 bits extra), + * 2 bytes (16 bits) requires 3 base-64 characters (18 bits, 2 bits extra), and + * 3 bytes (25 bits) requires 4 base-64 characters (24 bits) exactly */ q = count / 3u; r = count % 3u; - mid = r ? r + 1u : 0u; - right = (!r ? 0u : pad ? 4u : r + 1u); + /* Full alphabet */ + left = q * 4u + r; + /* Partial alphabet (extra bits are encoded as 0) */ + mid = r ? 1u : 0u; + /* Padding: r==0: no padding, + * r==1: 2 base-64 characters, so 2 padding characters, + * r==2: 3 base-64 characters, so 1 padding characters */ + right = (r && pad) ? 3u - r : 0u; + /* Get total length */ if (q > ((size_t)SSIZE_MAX - right) / 4u) goto erange; - left = q * 4u; ret += left + mid + right; + /* Make sure we don't write more random characters than + * we have room for; we add `mid` into `left` so we have + * one variable for all random characters */ left += mid; if (left > size) { - left = size; - mid = 0; + left = 0u; + mid = 0u; } + + /* Write random characters */ if (librecrypt_fill_with_random_(out_buffer, left, rng, user)) return -1; - for (j = 0; j < left; j++) - out_buffer[j] = lut[((unsigned char *)out_buffer)[j]]; - if (mid) - out_buffer[j] = lut[((unsigned char *)out_buffer)[j] & (r == 1u ? ~15u : ~3u)]; + for (i = 0u; i < left; i++) + out_buffer[i] = lut[((unsigned char *)out_buffer)[i]]; + if (mid) { + i = left - 1u; + out_buffer[i] = lut[((unsigned char *)out_buffer)[i] & (r == 1u ? ~15u : ~3u)]; + } out_buffer = &out_buffer[left]; size -= left; - if (right > size) - right = size; - for (j = 0; j < right; j++) + /* Write padding charaters */ + right = right < size ? right : size; + for (i = 0u; i < right; i++) out_buffer[right] = pad; out_buffer = &out_buffer[right]; size -= right; } + + /* Copy remainder of the algorithm configuration, and the '>' if intermediate */ + for (i = 0u; settings[i];) + if (settings[i++] == LIBRECRYPT_ALGORITHM_LINK_DELIMITER) + break; + min = i < size ? i : size; + memcpy(out_buffer, settings, min); + size -= min; + settings = &settings[i]; + ret += i; } + /* NUL-terminate the output if we were doing output */ if (nul_term) *out_buffer = '\0'; + + /* Return the number of written bytes (excluding NUL byte): + * the length of the new password hash string, but ensure + * the new salts where not so large that our return value + * is out of range */ if (ret > (size_t)SSIZE_MAX) goto erange; return (ssize_t)ret; -- cgit v1.2.3-70-g09d2