prole/pg-knoe-auth/test/unit/test_base64url.c
chrisfu 57886f9268 feat(pg-knoe-auth): import upstream PostgreSQL JWT auth extension; compile in knoe-db image
- Copy pg-knoe-auth/ wholesale from upstream/knoe-db/20260523 (Task 1 of
  docs/plans/junie/upstream-knoe-db-20260523-integration.md).
- Extension: PG18 OAUTHBEARER JWT validator using libcurl + OpenSSL RS256.
- knoe-db/Dockerfile: add libcurl4-openssl-dev to dev deps; COPY src/ and
  build with make USE_PGXS=1 install after tds_fdw.
- NOT enabled in the default database build (absent from 20_create_extensions.sh).
  To enable: CREATE EXTENSION pg_knoe_auth; (requires pg_hba.conf oauth_issuer).

Closes Task 1 of upstream-knoe-db-20260523-integration.md.
2026-05-23 21:52:06 -07:00

114 lines
3.5 KiB
C

/*
* 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 <openssl/bio.h>
#include <openssl/evp.h>
/*
* 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();
}