/* * test_base64url.c — unit tests for base64url decode (RFC 4648 §5 vectors). * * Tests correct decoding, missing padding handling, and bad input rejection. * See docs/plans/junie/pg_knoe_auth-rename-harden-modularize.md §2.5 */ #include "test_helpers.h" #include #include /* * base64url_decode — copied verbatim from pg_knoe_auth.c. */ static unsigned char * base64url_decode(const char *input, size_t input_len, size_t *out_len) { char *b64; size_t b64_len; size_t pad; unsigned char *result; int decoded_len; BIO *bio, *b64bio; b64_len = input_len; pad = (4 - (input_len % 4)) % 4; b64 = palloc(b64_len + pad + 2); memcpy(b64, input, input_len); for (size_t i = 0; i < input_len; i++) { if (b64[i] == '-') b64[i] = '+'; else if (b64[i] == '_') b64[i] = '/'; } for (size_t i = 0; i < pad; i++) b64[b64_len + i] = '='; b64[b64_len + pad] = '\n'; b64[b64_len + pad + 1] = '\0'; result = palloc(b64_len + pad + 4); b64bio = BIO_new(BIO_f_base64()); bio = BIO_new_mem_buf(b64, (int)(b64_len + pad + 1)); bio = BIO_push(b64bio, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); decoded_len = BIO_read(bio, result, (int)(b64_len + pad + 4)); BIO_free_all(bio); pfree(b64); if (decoded_len < 0) { pfree(result); return NULL; } *out_len = (size_t) decoded_len; return result; } int main(void) { unsigned char *out; size_t out_len; /* * RFC 4648 §10 test vectors (base64url form — '-' for '+', '_' for '/'). * Standard base64: "" → "" * "f" → "Zg==" * "fo" → "Zm8=" * "foo" → "Zm9v" * "foob" → "Zm9vYg==" * "fooba" → "Zm9vYmE=" * "foobar" → "Zm9vYmFy" * base64url strips padding. */ /* "f" → base64url "Zg" (no padding) */ out = base64url_decode("Zg", 2, &out_len); ASSERT(out != NULL && out_len == 1 && out[0] == 'f', "RFC4648 vector: 'Zg' decodes to 'f'"); if (out) pfree(out); /* "fo" → base64url "Zm8" */ out = base64url_decode("Zm8", 3, &out_len); ASSERT(out != NULL && out_len == 2 && out[0] == 'f' && out[1] == 'o', "RFC4648 vector: 'Zm8' decodes to 'fo'"); if (out) pfree(out); /* "foo" → base64url "Zm9v" */ out = base64url_decode("Zm9v", 4, &out_len); ASSERT(out != NULL && out_len == 3 && memcmp(out, "foo", 3) == 0, "RFC4648 vector: 'Zm9v' decodes to 'foo'"); if (out) pfree(out); /* "foobar" → base64url "Zm9vYmFy" */ out = base64url_decode("Zm9vYmFy", 8, &out_len); ASSERT(out != NULL && out_len == 6 && memcmp(out, "foobar", 6) == 0, "RFC4648 vector: 'Zm9vYmFy' decodes to 'foobar'"); if (out) pfree(out); /* base64url '-' and '_' substitution: encode "\xfb\xff" = "+/8=" in std, * "-_8" in base64url (no pad) */ out = base64url_decode("-_8", 3, &out_len); ASSERT(out != NULL && out_len == 2 && (unsigned char)out[0] == 0xfb && (unsigned char)out[1] == 0xff, "base64url '-' and '_' chars decoded correctly"); if (out) pfree(out); /* Empty input */ out = base64url_decode("", 0, &out_len); ASSERT(out != NULL && out_len == 0, "empty input decodes to empty output"); if (out) pfree(out); TEST_SUMMARY(); }