/* * 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 #include #include #include #include #include #include #include /* 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