aboutsummaryrefslogtreecommitdiffstats
path: root/librecrypt_decode.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-01 17:45:39 +0200
committerMattias Andrée <m@maandree.se>2026-05-01 17:45:39 +0200
commitadfa8e1265f6155d1a582baa9929af198bb5d4de (patch)
treee3cee62aa5a8768621cd294295f787b8cc54141b /librecrypt_decode.c
parentAdd librecrypt.7 and README (diff)
downloadlibrecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.gz
librecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.bz2
librecrypt-adfa8e1265f6155d1a582baa9929af198bb5d4de.tar.xz
Misc
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to 'librecrypt_decode.c')
-rw-r--r--librecrypt_decode.c110
1 files changed, 97 insertions, 13 deletions
diff --git a/librecrypt_decode.c b/librecrypt_decode.c
index ee451a7..847bfea 100644
--- a/librecrypt_decode.c
+++ b/librecrypt_decode.c
@@ -12,6 +12,15 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len,
unsigned char a, b, c, d;
size_t n = 0u;
+ /* For as many multiple of 4 input characters as available,
+ * it can be coded into multiples of 3 bytes (or less if
+ * padding is used), however we need to check that the output
+ * buffer is large enough. Even if `size` is 0, we need read
+ * all characters to ensure that they only use the valid
+ * characters in the encoding alphabet or that padding is
+ * used correct — we want to validate the string, and we do
+ * not support padding in the middle (it is technically
+ * doable, but there is no reason it would ever be used) */
for(; len >= 4u; str += 4u) {
len -= 4u;
@@ -44,16 +53,28 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len,
n++;
}
- if (len && strict_pad && pad)
+ /* If the number of input characters was not a multiple of
+ * four, the string is invalid if padding was required
+ * (`strict_pad` is ignored if `pad` is the NUL byte) */
+ if (len && pad && strict_pad)
goto einval;
+ /* Decode the remainder of the input, which shouldn't
+ * contain any padding since it is less than 4 bytes,
+ * and padding is done to multiples of 4 bytes */
switch (len) {
case 0u:
+ /* 0 characters left over, there was nothing left, and we are done */
goto out;
+
case 1u:
+ /* 1 character left over, this is invalid as at least 2 base-64
+ * characters is required to encode at least 1 byte */
goto einval;
default:
+ /* 2 or 3 characters left over */
+
a = lut[str[0u]];
b = lut[str[1u]];
if ((a | b) == 0xFFu)
@@ -66,7 +87,9 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len,
c = lut[str[2u]];
if (c == 0xFFu) {
- if (!pad || str[2u] != pad)
+ if (!pad)
+ goto einval;
+ if (str[2u] != pad)
goto einval;
break;
}
@@ -112,29 +135,41 @@ static const unsigned char lut[256u] = {
check((BINARY), sizeof(BINARY) - 1u, (ASCII PAD), sizeof(ASCII) - 1u, sizeof(ASCII PAD) - 1u)
+static int check_good_padding = 1;
+
+
static void
check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_len, size_t padded_len)
{
char buf[16u];
size_t i, j;
+ /* Internal test check */
assert(binary_len <= sizeof(buf));
- assert(padded_len % 4u == 0u);
- assert(padded_len / 4u == (unpadded_len + 3u) / 4u);
+ /* Internal test check */
+ if (check_good_padding) {
+ assert(padded_len % 4u == 0u);
+ assert(padded_len / 4u == (unpadded_len + 3u) / 4u);
+ }
assert(padded_len >= unpadded_len);
+ /* Test no output and without padding */
EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len);
EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 1) == (ssize_t)binary_len);
EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 0) == (ssize_t)binary_len);
-
if (padded_len == unpadded_len) {
EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == (ssize_t)binary_len);
} else {
+ /* illegal combination: padding missing but required */
errno = 0;
EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == -1);
EXPECT(errno == EINVAL);
+ }
+ /* Test no output and with padding */
+ if (padded_len != unpadded_len) {
+ /* illegal combinations: padding present no padding character defined (padded with illegal characters) */
errno = 0;
EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 0) == -1);
EXPECT(errno == EINVAL);
@@ -143,10 +178,11 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_
EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 1) == -1);
EXPECT(errno == EINVAL);
}
-
EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 0) == (ssize_t)binary_len);
- EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
+ if (check_good_padding)
+ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
+ /* Test output, with and without truncation */
for (i = 0u; i < sizeof(buf); i++) {
memset(buf, 99, sizeof(buf));
EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len);
@@ -197,12 +233,14 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_
for (; j < sizeof(buf); j++)
EXPECT(buf[j] == 99);
- memset(buf, 99, sizeof(buf));
- EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
- j = i < binary_len ? i : binary_len;
- EXPECT(!memcmp(buf, binary, j));
- for (; j < sizeof(buf); j++)
- EXPECT(buf[j] == 99);
+ if (check_good_padding) {
+ memset(buf, 99, sizeof(buf));
+ EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len);
+ j = i < binary_len ? i : binary_len;
+ EXPECT(!memcmp(buf, binary, j));
+ for (; j < sizeof(buf); j++)
+ EXPECT(buf[j] == 99);
+ }
}
}
@@ -215,6 +253,8 @@ main(void)
SET_UP_ALARM();
+ /* Normal test cases */
+ check_good_padding = 1;
CHECK("", "", "");
CHECK("\x00", "AA", "==");
CHECK("\x00\x00", "AAA", "=");
@@ -224,6 +264,14 @@ main(void)
CHECK("zy[]y21 !", "enlbXXkyMSAh", "");
CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw", "==");
+ /* Bad but acceptable case */
+ check_good_padding = 0;
+ CHECK("\x00", "AA", "=");
+
+ /* Test illegal characters, and 1 non-value character with
+ * 3 pad characeters (illegal since at least 2 base-64
+ * characters are needed to encode at least 1 byte) */
+ check_good_padding = 1;
for (i = 0; i <= 1; i++) {
errno = 0;
EXPECT(librecrypt_decode(NULL, 0u, "AA=*", 4u, lut, '=', i) == -1);
@@ -248,6 +296,42 @@ main(void)
errno = 0;
EXPECT(librecrypt_decode(NULL, 0u, "====", 4u, lut, '=', i) == -1);
EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "A*==", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "*A==", 4u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "A*", 2u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "*A", 2u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "=", 1u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "==", 2u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "=A", 2u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '=', i) == -1);
+ EXPECT(errno == EINVAL);
+
+ errno = 0;
+ EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '\0', i) == -1);
+ EXPECT(errno == EINVAL);
}
return 0;