Coverage Report

Created: 2026-08-31 07:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/test/fuzz/fuzz_jwks.c
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  Licensed under the Apache License,
4
 * Version 2.0 (the "License"); you may not use this file except in
5
 * compliance with the License.  You may obtain a copy of the License at
6
 *
7
 *   http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 * Copyright (C) 2017-2026 ZmartZone Holding BV - hans.zandbelt@openidc.com
10
 *
11
 * Fuzz target for the JWK / JWK Set parsers: a JWKS document is fetched from
12
 * the provider's jwks_uri, so its contents are under the control of whoever
13
 * answers at that URL -- the OP, or anyone who can influence the document in
14
 * a multi-provider (discovery / dynamic registration) setup. The same parser
15
 * handles the JWKs a client_secret-less client publishes for its own keys.
16
 *
17
 * The decoded JSON is routed the way the module routes it: a "keys" array is
18
 * parsed as a set (oidc_jwks_parse_json), a bare "kty" object as a single key
19
 * (oidc_jwk_parse_json). That covers the per-kty material parsing in
20
 * jose/jwk.c -- RSA n/e/d, EC crv/x/y, oct k, the optional x5c certificate
21
 * chain and x5t thumbprints -- and the re-serialization and default-alg
22
 * helpers are run over every key that parsed, so the round trip is stressed
23
 * with whatever the parser accepted rather than only with what it rejects.
24
 */
25
26
#include "fuzz.h"
27
/* util.h pulls in const.h before any Apache header does, so config.h's
28
 * PACKAGE_* defines win the race against Apache's own (empty) ones in
29
 * ap_config_auto.h -- json.h includes httpd.h directly; keep util.h ahead
30
 * of it (clang-format's include sorting would undo exactly that, hence the
31
 * guard) */
32
/* clang-format off */
33
#include "util.h"      /* test fixture */
34
#include "json.h"
35
#include "jose.h"
36
#include "util/util.h" /* oidc_json_decode_object */
37
/* clang-format on */
38
39
#include <apr_pools.h>
40
#include <apr_strings.h>
41
42
static int g_ready = 0;
43
44
/* engine-called one-time init, pre-forkserver on AFL++: see fuzz.h */
45
34
int LLVMFuzzerInitialize(int *argc, char ***argv) {
46
34
  (void)argc;
47
34
  (void)argv;
48
34
  if (!g_ready) {
49
34
    oidc_test_setup();
50
34
    g_ready = 1;
51
34
  }
52
34
  return 0;
53
34
}
54
55
/* run the helpers that consume a parsed key: serialization (full and public-only) and the default-alg lookup */
56
9.63k
static void fuzz_jwk_use(apr_pool_t *pool, const oidc_jwk_t *jwk) {
57
9.63k
  char *s_json = NULL;
58
9.63k
  oidc_jose_error_t err;
59
9.63k
  oidc_jwk_to_json(pool, jwk, &s_json, &err);
60
9.63k
  oidc_jwk_to_public_json(pool, jwk, &s_json, &err);
61
9.63k
  oidc_jwk_default_jws_alg(jwk);
62
  /* a copy duplicates the OpenSSL key material outside the pool; destroy it the same way */
63
9.63k
  oidc_jwk_t *copy = oidc_jwk_copy(pool, jwk);
64
9.63k
  if (copy != NULL)
65
9.63k
    oidc_jwk_destroy(copy);
66
9.63k
}
67
68
7.04k
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
69
7.04k
  if (!g_ready)
70
0
    LLVMFuzzerInitialize(NULL, NULL);
71
72
7.04k
  apr_pool_t *pool = NULL;
73
7.04k
  apr_pool_create(&pool, oidc_test_pool_get());
74
75
7.04k
  request_rec r = *oidc_test_request_get();
76
7.04k
  r.pool = pool;
77
78
7.04k
  char *s = apr_pstrmemdup(pool, (const char *)data, size);
79
7.04k
  oidc_json_t *json = NULL;
80
7.04k
  if ((oidc_json_decode_object(&r, s, &json) == TRUE) && (json != NULL)) {
81
5.29k
    oidc_jose_error_t err;
82
5.29k
    if (oidc_is_jwks(json) == TRUE) {
83
1.46k
      apr_array_header_t *jwk_list = NULL;
84
1.46k
      apr_byte_t ok = oidc_jwks_parse_json(pool, json, &jwk_list, &err);
85
1.46k
      if ((ok == TRUE) && (jwk_list != NULL))
86
9.69k
        for (int i = 0; i < jwk_list->nelts; i++)
87
9.33k
          fuzz_jwk_use(pool, APR_ARRAY_IDX(jwk_list, i, const oidc_jwk_t *));
88
      /* the keys wrap cjose/OpenSSL objects allocated outside the pool */
89
1.46k
      if (jwk_list != NULL)
90
362
        oidc_jwk_list_destroy(jwk_list);
91
3.83k
    } else if (oidc_is_jwk(json) == TRUE) {
92
3.79k
      oidc_jwk_t *jwk = NULL;
93
3.79k
      if ((oidc_jwk_parse_json(pool, json, &jwk, &err) == TRUE) && (jwk != NULL)) {
94
298
        fuzz_jwk_use(pool, jwk);
95
298
        oidc_jwk_destroy(jwk);
96
298
      }
97
3.79k
    }
98
5.29k
    oidc_json_decref(json); /* jansson value is refcounted, not pooled */
99
5.29k
  }
100
101
7.04k
  apr_pool_destroy(pool);
102
7.04k
  return 0;
103
7.04k
}