busybox/libbb/yescrypt/alg-yescrypt-common.c
Denys Vlasenko b823735b7e libbb/yescrypt: actually, largest allowed salt is 86 chars, support that
function                                             old     new   delta
yescrypt_r                                           767     756     -11

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2025-07-17 17:01:40 +02:00

408 lines
11 KiB
C

/*-
* Copyright 2013-2018 Alexander Peslyak
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#if RESTRICTED_PARAMS
#define decode64_uint32(dst, src, min) \
({ \
uint32_t d32 = a2i64(*(src)); \
if (d32 > 47) \
goto fail; \
*(dst) = d32 + (min); \
++src; \
})
#define test_decode64_uint32() ((void)0)
#define FULL_PARAMS(...)
#else
#define FULL_PARAMS(...) __VA_ARGS__
/* Not inlining:
* de/encode64 functions are only used to read
* yescrypt_params_t field, and convert salt to binary -
* both of these are negligible compared to main hashing operation
*/
static NOINLINE const uint8_t *decode64_uint32(
uint32_t *dst,
const uint8_t *src, uint32_t val)
{
uint32_t start = 0, end = 47, bits = 0;
uint32_t c;
if (!src) /* previous decode failed already? */
goto fail;
c = a2i64(*src++);
if (c > 63)
goto fail;
// The encoding of number N:
// start = 0 end = 47
// If N < 48, it is encoded verbatim, else
// N -= 48
// start = end+1 = 48
// end += (64-end)/2 = 55
// If N < (end+1-start)<<6 = 8<<6, it is encoded as 48+(N>>6)|low6bits (that is, 48...55|<6bit>), else
// N -= 8<<6
// start = end+1 = 56
// end += (64-end)/2 = 59
// If N < (end+1-start)<<2*6 = 4<<12, it is encoded as 56+(N>>2*6)|low12bits (that is, 56...59|<6bit>|<6bit>), else
// ...same for 60..61|<6bit>|<6bit>|<6bit>
// .......same for 62|<6bit>|<6bit>|<6bit>|<6bit>
// .......same for 63|<6bit>|<6bit>|<6bit>|<6bit>|<6bit>
dbg_dec64("c:%d val:0x%08x", (int)c, (unsigned)val);
while (c > end) {
dbg_dec64("c:%d > end:%d", (int)c, (int)end);
val += (end + 1 - start) << bits;
dbg_dec64("val+=0x%08x", (int)((end + 1 - start) << bits));
dbg_dec64(" val:0x%08x", (unsigned)val);
start = end + 1;
end += (64 - end) / 2;
bits += 6;
dbg_dec64("start=%d", (int)start);
dbg_dec64("end=%d", (int)end);
dbg_dec64("bits=%d", (int)bits);
}
val += (c - start) << bits;
dbg_dec64("final val+=0x%08x", (int)((c - start) << bits));
dbg_dec64(" val:0x%08x", (unsigned)val);
while (bits != 0) {
c = a2i64(*src++);
if (c > 63)
goto fail;
bits -= 6;
val += c << bits;
dbg_dec64("low bits val+=0x%08x", (int)(c << bits));
dbg_dec64(" val:0x%08x", (unsigned)val);
}
ret:
*dst = val;
return src;
fail:
val = 0;
src = NULL;
goto ret;
}
#if TEST_DECODE64
static void test_decode64_uint32(void)
{
const uint8_t *src, *end;
uint32_t u32;
int a = 48;
int b = 8<<6; // 0x0200
int c = 4<<12; // 0x04000
int d = 2<<18; // 0x080000
int e = 1<<24; // 0x1000000
src = (void*)"wzzz";
end = decode64_uint32(&u32, src, 0);
if (u32 != 0x0003ffff+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32);
if (end != src + 4) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end);
src = (void*)"xzzz";
end = decode64_uint32(&u32, src, 0);
if (u32 != 0x0007ffff+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32);
if (end != src + 4) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end);
// Note how the last representable "x---" encoding, 0x7ffff, is exactly d-1!
// And if we now increment it, we get:
src = (void*)"y....";
end = decode64_uint32(&u32, src, 0);
if (u32 != 0x00000000+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32);
if (end != src + 5) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end);
src = (void*)"yzzzz";
end = decode64_uint32(&u32, src, 0);
if (u32 != 0x00ffffff+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32);
if (end != src + 5) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end);
src = (void*)"zzzzzz";
end = decode64_uint32(&u32, src, 0);
if (u32 != 0x3fffffff+e+d+c+b+a) bb_error_msg_and_die("Incorrect decode '%s':0x%08x", src, (unsigned)u32);
if (end != src + 6) bb_error_msg_and_die("Incorrect decode '%s': %p end:%p", src, src, end);
bb_error_msg("test_decode64_uint32() OK");
}
#else
# define test_decode64_uint32() ((void)0)
#endif
#endif /* !RESTRICTED_PARAMS */
#if 1
static const uint8_t *decode64(
uint8_t *dst, size_t *dstlen,
const uint8_t *src)
{
unsigned dstpos = 0;
dbg_dec64("src:'%s'", src);
for (;;) {
uint32_t c, value = 0;
int bits = 0;
while (*src != '\0' && *src != '$') {
c = a2i64(*src);
if (c > 63) { /* bad ascii64 char, stop decoding at it */
break;
}
src++;
value |= c << bits;
bits += 6;
if (bits == 24) /* got 4 chars */
goto store;
}
/* we read entire src, or met a non-ascii64 char (such as "$") */
if (bits == 0)
break;
/* else: we got last, partial bit block - store it */
store:
dbg_dec64(" storing bits:%d dstpos:%u v:%08x", bits, dstpos, (int)SWAP_BE32(value)); //BE to see lsb first
for (;;) {
if ((*src == '\0' || *src == '$')
&& value == 0 && bits < 8
) {
/* Example: mkpasswd PWD '$y$j9T$123':
* the "123" is bits:18 value:03,51,00
* is considered to be 2 bytes, not 3!
*
* '$y$j9T$zzz' in upstream fails outright (3rd byte isn't zero).
* IOW: for upstream, validity of salt depends on VALUE,
* not just size of salt. Which is a bug.
* The '$y$j9T$zzz.' salt is the same
* (it adds 6 zero msbits) but upstream works with it,
* thus '$y$j9T$zzz' should work too and give the same result.
*/
goto end;
}
if (dstpos >= *dstlen) {
dbg_dec64(" ERR: bits:%d dstpos:%u dst[] is too small", bits, dstpos);
goto fail;
}
*dst++ = value;
dstpos++;
value >>= 8;
bits -= 8;
if (bits <= 0) /* can get negative, if we e.g. had 6 bits */
break;
}
if (*src == '\0' || *src == '$')
break;
}
end:
*dstlen = dstpos;
dbg_dec64("dec64: OK, dst[%d]", (int)dstpos);
return src;
fail:
/* *dstlen = 0; - not needed, caller detects error by seeing NULL */
return NULL;
}
#else
/* Buggy (and larger) original code */
static const uint8_t *decode64(
uint8_t *dst, size_t *dstlen,
const uint8_t *src, size_t srclen)
{
size_t dstpos = 0;
while (dstpos <= *dstlen && srclen) {
uint32_t value = 0, bits = 0;
while (srclen--) {
uint32_t c = a2i64(*src);
if (c > 63) {
srclen = 0;
break;
}
src++;
value |= c << bits;
bits += 6;
if (bits >= 24)
break;
}
if (!bits)
break;
if (bits < 12) /* must have at least one full byte */
goto fail;
dbg_dec64(" storing bits:%d v:%08x", (int)bits, (int)SWAP_BE32(value)); //BE to see lsb first
while (dstpos++ < *dstlen) {
*dst++ = value;
value >>= 8;
bits -= 8;
if (bits < 8) { /* 2 or 4 */
if (value) /* must be 0 */
goto fail;
bits = 0;
break;
}
}
if (bits)
goto fail;
}
if (!srclen && dstpos <= *dstlen) {
*dstlen = dstpos;
dbg_dec64("dec64: OK, dst[%d]", (int)dstpos);
return src;
}
fail:
/* *dstlen = 0; - not needed, caller detects error by seeing NULL */
return NULL;
}
#endif
static char *encode64(
char *dst, size_t dstlen,
const uint8_t *src, size_t srclen)
{
while (srclen) {
uint32_t value = 0, b = 0;
do {
value |= (uint32_t)(*src++ << b);
b += 8;
srclen--;
} while (srclen && b < 24);
b >>= 3; /* number of bits to number of bytes */
b++; /* 1, 2 or 3 bytes will become 2, 3 or 4 ascii64 chars */
dstlen -= b;
if ((ssize_t)dstlen <= 0)
return NULL;
dst = num2str64_lsb_first(dst, value, b);
}
*dst = '\0';
return dst;
}
char *yescrypt_r(
const uint8_t *passwd, size_t passwdlen,
const uint8_t *setting,
char *buf, size_t buflen)
{
struct {
yescrypt_ctx_t yctx[1];
unsigned char hashbin32[32];
} u;
#define yctx u.yctx
#define hashbin32 u.hashbin32
char *dst;
const uint8_t *src, *saltend;
size_t need, prefixlen;
uint32_t u32;
test_decode64_uint32();
memset(yctx, 0, sizeof(yctx));
FULL_PARAMS(yctx->param.p = 1;)
/* we assume setting starts with "$y$" (caller must ensure this) */
src = setting + 3;
src = decode64_uint32(&yctx->param.flags, src, 0);
/* "j9T" returns: 0x2f */
//if (!src)
// goto fail;
if (yctx->param.flags < YESCRYPT_RW) {
dbg("yctx->param.flags=0x%x", (unsigned)yctx->param.flags);
goto fail; // bbox: we don't support scrypt - only yescrypt
} else if (yctx->param.flags <= YESCRYPT_RW + (YESCRYPT_RW_FLAVOR_MASK >> 2)) {
/* "j9T" sets flags to 0xb6 */
yctx->param.flags = YESCRYPT_RW + ((yctx->param.flags - YESCRYPT_RW) << 2);
dbg("yctx->param.flags=0x%x", (unsigned)yctx->param.flags);
dbg(" YESCRYPT_RW:%u", !!(yctx->param.flags & YESCRYPT_RW));
dbg((yctx->param.flags & YESCRYPT_RW_FLAVOR_MASK) ==
(YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K)
? " YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_4 | YESCRYPT_SIMPLE_2 | YESCRYPT_SBOX_12K"
: " flags are not standard"
);
} else {
goto fail;
}
src = decode64_uint32(&u32, src, 1);
if (/*!src ||*/ u32 > 63)
goto fail;
yctx->param.N = (uint64_t)1 << u32;
/* "j9T" sets to 4096 (1<<12) */
dbg("yctx->param.N=%llu (1<<%u)", (unsigned long long)yctx->param.N, (unsigned)u32);
src = decode64_uint32(&yctx->param.r, src, 1);
/* "j9T" sets to 32 */
dbg("yctx->param.r=%u", yctx->param.r);
if (!src)
goto fail;
if (*src != '$') {
#if RESTRICTED_PARAMS
goto fail;
#else
src = decode64_uint32(&u32, src, 1);
dbg("yescrypt has extended params:0x%x", (unsigned)u32);
if (u32 & 1)
src = decode64_uint32(&yctx->param.p, src, 2);
if (u32 & 2)
src = decode64_uint32(&yctx->param.t, src, 1);
if (u32 & 4)
src = decode64_uint32(&yctx->param.g, src, 1);
if (u32 & 8) {
src = decode64_uint32(&u32, src, 1);
if (/*!src ||*/ u32 > 63)
goto fail;
yctx->param.NROM = (uint64_t)1 << u32;
}
if (!src)
goto fail;
if (*src != '$')
goto fail;
#endif
}
yctx->saltlen = sizeof(yctx->salt);
src++; /* now points to salt */
saltend = decode64(yctx->salt, &yctx->saltlen, src);
if (!saltend || (*saltend != '\0' && *saltend != '$'))
goto fail; /* salt[] is too small, or bad char during decode */
dbg_dec64("salt is %d ascii64 chars -> %d bytes (in binary)", (int)(saltend - src), (int)yctx->saltlen);
prefixlen = saltend - setting;
need = prefixlen + 1 + YESCRYPT_HASH_LEN + 1;
if (need > buflen /*overflow is quite unlikely: || need < prefixlen*/)
goto fail;
if (yescrypt_kdf32(yctx, passwd, passwdlen, hashbin32)) {
dbg("error in yescrypt_kdf32");
goto fail;
}
dst = mempcpy(buf, setting, prefixlen);
*dst++ = '$';
dst = encode64(dst, buflen - (dst - buf), hashbin32, sizeof(hashbin32));
if (!dst)
goto fail;
ret:
free_region(yctx->local);
explicit_bzero(&u, sizeof(u));
return buf;
fail:
buf = NULL;
goto ret;
#undef yctx
#undef hashbin32
}