Files
heimdal/lib/hx509/fuzz_jose.c
2026-01-21 11:45:28 -06:00

270 lines
7.4 KiB
C

/*
* Copyright (c) 2025 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE 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 INSTITUTE 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.
*/
/*
* libFuzzer harness for JWS/JWT parsing in jose.c
*
* Build with:
* clang -g -O1 -fno-omit-frame-pointer -fsanitize=fuzzer,address \
* -I... fuzz_jose.c -o fuzz_jose -lhx509 -lroken ...
*
* Run with:
* ./fuzz_jose corpus_dir/
*/
#include <config.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <hx509.h>
#include <getarg.h>
#include <roken.h>
/* libFuzzer entry points */
int LLVMFuzzerInitialize(int *argc, char ***argv);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
/* Test keys for signature verification fuzzing */
/* RSA-2048 public key */
static const char *rsa_pubkey_pem =
"-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofgWCuLjybRlzo0tZWJj\n"
"NiuSfb4p4fAkd/wWJcyQoTbji9k0l8W26mPddxHmfHQp+Vaw+4qPCJrcS2mJPMEz\n"
"P1Pt0Bm4d4QlL+yRT+SFd2lZS+pCgNMsD1W/YpRPEwOWvG6b32690r2jZ47soMZo\n"
"9wGzjb/7OMg0LOL+bSf63kpaSHSXndS5z5rexMdbBYUsLA9e+KXBdQOS+UTo7WTB\n"
"EMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6/I5IhlJH7aGhyxX\n"
"FvUK+DWNmoudF8NAco9/h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXp\n"
"oQIDAQAB\n"
"-----END PUBLIC KEY-----\n";
/* EC P-256 public key */
static const char *ec_pubkey_pem =
"-----BEGIN PUBLIC KEY-----\n"
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf83OJ3D2xF1Bg8vub9tLe1gHMzV7\n"
"6e8Tus9uPHvRVEXH8UTNG72bfocs3+257rn0s2ldbqkLJK2KRiMohYjlrQ==\n"
"-----END PUBLIC KEY-----\n";
/* Ed25519 public key */
static const char *ed25519_pubkey_pem =
"-----BEGIN PUBLIC KEY-----\n"
"MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=\n"
"-----END PUBLIC KEY-----\n";
static hx509_context ctx = NULL;
/*
* Initialize hx509 context once.
* Called by libFuzzer before fuzzing starts.
*/
int LLVMFuzzerInitialize(int *argc, char ***argv)
{
(void)argc;
(void)argv;
if (hx509_context_init(&ctx) != 0) {
ctx = NULL;
}
return 0;
}
/*
* Main fuzzing entry point.
* Input is treated as a potential JWS/JWT token.
*/
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
char *token = NULL;
void *payload = NULL;
size_t payload_len = 0;
heim_dict_t claims = NULL;
const char *keys[3];
int ret;
if (ctx == NULL)
return 0;
/* Need at least "a.b.c" for a valid JWS structure */
if (size < 5)
return 0;
/* Limit input size to avoid OOM */
if (size > 1024 * 1024)
return 0;
/* Make null-terminated copy */
token = malloc(size + 1);
if (token == NULL)
return 0;
memcpy(token, data, size);
token[size] = '\0';
/* Set up key array for verification attempts */
keys[0] = rsa_pubkey_pem;
keys[1] = ec_pubkey_pem;
keys[2] = ed25519_pubkey_pem;
/*
* Test 1: JWS verification with multiple key types.
* This exercises:
* - Base64URL decoding of header, payload, signature
* - JSON parsing of header
* - Algorithm detection and validation
* - Key type matching
* - Signature format handling (ECDSA JWS->DER conversion)
*/
ret = hx509_jws_verify(ctx, token, keys, 3, &payload, &payload_len);
if (ret == 0) {
free(payload);
payload = NULL;
}
/*
* Test 2: JWT verification (includes claims parsing).
* This exercises:
* - Everything from JWS verification
* - JSON parsing of claims payload
* - Claims validation (exp, nbf, aud)
*/
ret = hx509_jwt_verify(ctx, token, keys, 3, NULL, 0, &claims);
if (ret == 0 && claims) {
heim_release(claims);
claims = NULL;
}
/*
* Test 3: Try with just one key at a time.
* This ensures we hit different code paths for key type mismatches.
*/
keys[0] = rsa_pubkey_pem;
ret = hx509_jws_verify(ctx, token, keys, 1, &payload, &payload_len);
if (ret == 0) {
free(payload);
payload = NULL;
}
keys[0] = ec_pubkey_pem;
ret = hx509_jws_verify(ctx, token, keys, 1, &payload, &payload_len);
if (ret == 0) {
free(payload);
payload = NULL;
}
keys[0] = ed25519_pubkey_pem;
ret = hx509_jws_verify(ctx, token, keys, 1, &payload, &payload_len);
if (ret == 0) {
free(payload);
payload = NULL;
}
/* Clear any error state */
hx509_clear_error_string(ctx);
free(token);
return 0;
}
#ifndef HAS_LIBFUZZER_MAIN
/*
* Standalone mode for testing without libFuzzer.
* Reads input from stdin or file arguments.
*/
static int help_flag;
static int version_flag;
static struct getargs args[] = {
{ "help", 'h', arg_flag, &help_flag, "Print help message", NULL },
{ "version", 0, arg_flag, &version_flag, "Print version", NULL }
};
static void
usage(int ret)
{
arg_printusage(args, sizeof(args)/sizeof(args[0]), NULL, "[FILE...]");
exit(ret);
}
int main(int argc, char **argv)
{
uint8_t buf[1024 * 1024];
size_t len;
FILE *fp;
int i;
int optidx = 0;
setprogname(argv[0]);
if (getarg(args, sizeof(args)/sizeof(args[0]), argc, argv, &optidx))
usage(1);
if (help_flag)
usage(0);
if (version_flag) {
print_version(NULL);
exit(0);
}
argc -= optidx;
argv += optidx;
LLVMFuzzerInitialize(&argc, &argv);
if (argc < 1) {
/* Read from stdin */
len = fread(buf, 1, sizeof(buf), stdin);
if (len > 0)
LLVMFuzzerTestOneInput(buf, len);
} else {
/* Read from each file argument */
for (i = 0; i < argc; i++) {
fp = fopen(argv[i], "rb");
if (fp == NULL)
continue;
len = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
if (len > 0)
LLVMFuzzerTestOneInput(buf, len);
}
}
if (ctx)
hx509_context_free(&ctx);
return 0;
}
#endif