base64: fix invalid compute on binary data

Wed, 02 Jun 2021 15:48:33 +0200

author
David Demelier <markand@malikania.fr>
date
Wed, 02 Jun 2021 15:48:33 +0200
changeset 31
16f64f665b1d
parent 30
74316437d97e
child 32
c96f2b26678a

base64: fix invalid compute on binary data

While here, add support for base64url decoding.

Makefile file | annotate | diff | comparison | revisions
base64.c file | annotate | diff | comparison | revisions
test/base64.c file | annotate | diff | comparison | revisions
--- a/Makefile	Mon May 17 14:02:41 2021 +0200
+++ b/Makefile	Wed Jun 02 15:48:33 2021 +0200
@@ -20,8 +20,8 @@
 
 CC=             cc
 CXX=            c++
-CFLAGS=         -O3 -DNDEBUG
-CXXFLAGS=       -std=c++17 -O3 -DNDEBUG
+CFLAGS=         -O3 -DNDEBUG -Wall -Wextra
+CXXFLAGS=       -std=c++17 -O3 -DNDEBUG -Wall -Wextra
 
 .SUFFIXES:
 .SUFFIXES: .c .cpp .o
--- a/base64.c	Mon May 17 14:02:41 2021 +0200
+++ b/base64.c	Wed Jun 02 15:48:33 2021 +0200
@@ -26,7 +26,7 @@
 int
 b64_isbase64(unsigned char ch)
 {
-	return isalnum(ch) || ch == '+' || ch == '/';
+	return isalnum(ch) || ch == '+' || ch == '-' || ch == '_' || ch == '/';
 }
 
 int
@@ -57,7 +57,8 @@
 	if (ch >= 'a' && ch <= 'z')
 		return ch - 71;
 
-	return ch == '+' ? 62U : 63U;
+	/* '-' is base64url support. */
+	return ch == '+' || ch == '-' ? 62U : 63U;
 }
 
 size_t
@@ -139,49 +140,46 @@
 		srcsz = strlen(src);
 
 	while (srcsz && dstsz) {
-		unsigned int parsed = 0, required, inputbuf[4] = {0};
+		int i = 0, r = 3;
+		unsigned int inputbuf[4] = {0};
 
-		for (; srcsz && parsed < 4; parsed++) {
-			/* '=' is only allowed in last 2 characters. */
-			if ((*src == '=' && parsed <= 1) || !b64_isvalid(*src))
+		for (; srcsz && i < 4; i++) {
+			if (*src == '=') {
+				/*
+				 * '=' is only allowed in last 2 characters,
+				 * otherwise it means we need less data.
+				 */
+				if (i <= 1)
+					goto eilseq;
+
+				/* Less data required. */
+				--r;
+			} else if (!b64_isvalid(*src))
 				goto eilseq;
+
 			if (b64_isbase64(*src))
-				inputbuf[parsed] = b64_rlookup(*src);
+				inputbuf[i] = b64_rlookup(*src);
 
 			++src;
 			--srcsz;
 		}
 
-		if (parsed != 4)
+		/* Make sure we haven't seen AB=Z as well. */
+		if (i != 4 || (src[-2] == '=' && src[-1] != '='))
 			goto eilseq;
-
-		if (inputbuf[3])
-			required = 3;
-		else if (inputbuf[2])
-			required = 2;
-		else
-			required = 1;
-
-		if (required >= dstsz)
+		if ((size_t)r >= dstsz)
 			goto erange;
 
 		*dst++ = ((inputbuf[0] << 2) & 0xfc) |
 		         ((inputbuf[1] >> 4) & 0x03);
 
-		if (inputbuf[2])
-			*dst++ = ((inputbuf[1] << 4) & 0xf0) |
-			         ((inputbuf[2] >> 2) & 0x0f);
-		if (inputbuf[3]) {
-			/* "XY=Z" is not allowed. */
-			if (inputbuf[2] == 0)
-				goto eilseq;
+		if (r >= 2)
+			*dst++ = ((inputbuf[1] << 4) & 0xf0) | ((inputbuf[2] >> 2) & 0x0f);
+		if (r >= 3)
+			*dst++ = ((inputbuf[2] << 6) & 0xc0) | (inputbuf[3] & 0x3f);
 
-			*dst++ = ((inputbuf[2] << 6) & 0xc0) |
-			          (inputbuf[3] & 0x3f);
-		}
-
-		nwritten += required;
-		dstsz -= required;
+		nwritten += r;
+		dstsz -= r;
 	}
 
 	/* Not enough room to store '\0'. */
--- a/test/base64.c	Mon May 17 14:02:41 2021 +0200
+++ b/test/base64.c	Wed Jun 02 15:48:33 2021 +0200
@@ -262,27 +262,57 @@
 GREATEST_TEST
 test_b64_decode_basic(void)
 {
+	static const struct {
+		const char *entry;
+		const char *exp;
+		size_t expsz;
+	} examples[] = {
+		{ "YQ==", "a", 1U },
+		{ "YWI=", "ab", 2U },
+		{ "YWJj", "abc", 3U },
+		{ "aGVsbG8=", "hello", 5U },
+		{ "dGhpcyBpcyBhIGxvbmcgc2VudGVuY2U=", "this is a long sentence", 23U },
+		{ "V2VsY29tZSB0byBvdXIgc2VydmVyIGR1ZGU=", "Welcome to our server dude", 26U },
+		{ NULL, NULL, 0 }
+	};
+
 	char buffer[BUFSIZ];
 	size_t r;
 
-	r = b64_decode("YQ==", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 1U);
-	GREATEST_ASSERT_STR_EQ(buffer, "a");
-	r = b64_decode("YWI=", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 2U);
-	GREATEST_ASSERT_STR_EQ(buffer, "ab");
-	r = b64_decode("YWJj", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 3U);
-	GREATEST_ASSERT_STR_EQ(buffer, "abc");
-	r = b64_decode("aGVsbG8=", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 5U);
-	GREATEST_ASSERT_STR_EQ(buffer, "hello");
-	r = b64_decode("dGhpcyBpcyBhIGxvbmcgc2VudGVuY2U=", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 23U);
-	GREATEST_ASSERT_STR_EQ(buffer, "this is a long sentence");
-	r = b64_decode("V2VsY29tZSB0byBvdXIgc2VydmVyIGR1ZGU=", -1, buffer, sizeof (buffer));
-	GREATEST_ASSERT_EQ(r, 26U);
-	GREATEST_ASSERT_STR_EQ(buffer, "Welcome to our server dude");
+	for (size_t i = 0; examples[i].entry; ++i) {
+		r = b64_decode(examples[i].entry, -1, buffer, sizeof (buffer));
+		GREATEST_ASSERT_EQ(r, examples[i].expsz);
+		GREATEST_ASSERT_STR_EQ(buffer, examples[i].exp);
+	}
+
+	GREATEST_PASS();
+}
+
+GREATEST_TEST
+test_b64_decode_binary(void)
+{
+	static const struct {
+		const char *entry;
+		size_t expsz;
+	} examples[] = {
+		{ "4oARcAAAAgoogyHe", 12 },
+		{ "ERADdw==", 4 },
+		{ "4oARkaUCAGAbAtfN", 12 },
+		{ NULL, 0 }
+	};
+
+	char bin[BUFSIZ], b64[BUFSIZ];
+	size_t r;
+
+	for (size_t i = 0; examples[i].entry; ++i) {
+		/* Compare by decoding and coding back again. */
+		r = b64_decode(examples[i].entry, -1, bin, sizeof (bin));
+		GREATEST_ASSERT_EQ(r, examples[i].expsz);
+		r = b64_encode(bin, r, b64, sizeof (b64));
+		GREATEST_ASSERT_EQ(r, strlen(examples[i].entry));
+		GREATEST_ASSERT_STR_EQ(b64, examples[i].entry);
+	}
+
 	GREATEST_PASS();
 }
 
@@ -407,6 +437,7 @@
 GREATEST_SUITE(suite_b64_decode)
 {
 	GREATEST_RUN_TEST(test_b64_decode_basic);
+	GREATEST_RUN_TEST(test_b64_decode_binary);
 	GREATEST_RUN_TEST(test_b64_decode_sized);
 	GREATEST_RUN_TEST(test_b64_decode_invalid);
 	GREATEST_RUN_TEST(test_b64_decode_toosmall);

mercurial