Coverage Report

Created: 2026-11-12 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/jose/jws.c
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19
20
/***************************************************************************
21
 * Copyright (C) 2017-2026 ZmartZone Holding BV
22
 * Copyright (C) 2013-2017 Ping Identity Corporation
23
 * All rights reserved.
24
 *
25
 * DISCLAIMER OF WARRANTIES:
26
 *
27
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
28
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
29
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
30
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
31
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
32
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
33
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
34
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
35
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
36
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
37
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40
 *
41
 * JSON Web Signature (JWS) / JWT signing, parsing and verification
42
 *
43
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
44
 */
45
46
#include "jose.h"
47
48
#include "jose/internal.h"
49
50
#include <jansson.h>
51
52
#include <cjose/cjose.h>
53
54
#include "util/util.h"
55
56
/*
57
 * set a header value in a JWT
58
 */
59
20.3k
void oidc_jwt_hdr_set(oidc_jwt_t *jwt, const char *key, const char *value) {
60
20.3k
  json_object_set_new(jwt->header.value.json, key, json_string(value));
61
20.3k
}
62
63
/*
64
 * create a new JWT
65
 */
66
21.7k
oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload) {
67
21.7k
  oidc_jwt_t *jwt = apr_pcalloc(pool, sizeof(oidc_jwt_t));
68
21.7k
  if (create_header) {
69
12.4k
    jwt->header.value.json = json_object();
70
12.4k
  }
71
21.7k
  if (create_payload) {
72
0
    jwt->payload.value.json = json_object();
73
0
  }
74
21.7k
  return jwt;
75
21.7k
}
76
77
/*
78
 * get a header value from a JWT
79
 */
80
570
const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key) {
81
570
  cjose_err cjose_err;
82
570
  cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws);
83
570
  return hdr ? cjose_header_get(hdr, key, &cjose_err) : NULL;
84
570
}
85
86
/*
87
 * set a JWT header member to a raw (pre-serialized) JSON value
88
 */
89
0
apr_byte_t oidc_jwt_hdr_set_json(oidc_jwt_t *jwt, const char *key, const char *raw_json, oidc_jose_error_t *err) {
90
0
  json_error_t json_error;
91
0
  json_t *value = json_loads(raw_json, 0, &json_error);
92
0
  if (value == NULL) {
93
0
    oidc_jose_error(err, "json_loads failed: %s", json_error.text);
94
0
    return FALSE;
95
0
  }
96
0
  json_object_set_new(jwt->header.value.json, key, value);
97
0
  return TRUE;
98
0
}
99
100
/* base64url-encoded {"alg":"none"} JOSE header */
101
99
#define OIDC_JOSE_HDR_ALG_NONE "eyJhbGciOiJub25lIn0"
102
103
/*
104
 * perform compact serialization on a JWT and return the resulting string
105
 */
106
4.83k
char *oidc_jose_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) {
107
4.83k
  cjose_err cjose_err;
108
4.83k
  char *result = NULL;
109
4.83k
  const char *s_payload = NULL;
110
4.83k
  char *out = NULL;
111
4.83k
  size_t out_len;
112
113
4.83k
  if (_oidc_strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) == 0) {
114
115
99
    s_payload =
116
99
        oidc_json_encode(pool, jwt->payload.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT);
117
99
    if (s_payload == NULL) {
118
0
      oidc_jose_error(err, "oidc_util_encode_json failed");
119
0
      return NULL;
120
0
    }
121
122
    // out is allocated by cjose and must be freed explicitly by cjose_get_dealloc()()
123
99
    if (cjose_base64url_encode((const uint8_t *)s_payload, _oidc_strlen(s_payload), &out, &out_len,
124
99
             &cjose_err) == FALSE) {
125
0
      oidc_jose_error(err, "cjose_base64url_encode failed: %s", oidc_cjose_e2s(pool, cjose_err));
126
0
      return NULL;
127
0
    }
128
129
99
    result = apr_pstrmemdup(pool, out, out_len);
130
99
    cjose_get_dealloc()(out);
131
99
    result = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, result);
132
133
4.73k
  } else {
134
135
    // out: "the returned string pointer is owned by the JWS, the caller should not attempt to free it
136
    // directly"
137
4.73k
    if (cjose_jws_export(jwt->cjose_jws, (const char **)&out, &cjose_err) == FALSE) {
138
0
      oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err));
139
0
      return NULL;
140
0
    }
141
142
4.73k
    result = apr_pstrdup(pool, out);
143
4.73k
  }
144
145
4.83k
  return result;
146
4.83k
}
147
148
/*
149
 * return the key type for an algorithm
150
 */
151
16.8k
int oidc_alg2kty(const char *alg) {
152
16.8k
  if (_oidc_strcmp(alg, CJOSE_HDR_ALG_DIR) == 0)
153
11.0k
    return CJOSE_JWK_KTY_OCT;
154
5.86k
  if (_oidc_strncmp(alg, "RS", 2) == 0)
155
382
    return CJOSE_JWK_KTY_RSA;
156
5.48k
  if (_oidc_strncmp(alg, "PS", 2) == 0)
157
243
    return CJOSE_JWK_KTY_RSA;
158
5.24k
  if (_oidc_strncmp(alg, "HS", 2) == 0)
159
4.21k
    return CJOSE_JWK_KTY_OCT;
160
1.03k
#if (OIDC_JOSE_EC_SUPPORT)
161
1.03k
  if (_oidc_strncmp(alg, "ES", 2) == 0)
162
234
    return CJOSE_JWK_KTY_EC;
163
796
#endif
164
796
  if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) ||
165
713
      (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0))
166
292
    return CJOSE_JWK_KTY_OCT;
167
504
  if (_oidc_strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP) == 0)
168
0
    return CJOSE_JWK_KTY_RSA;
169
504
  return -1;
170
504
}
171
172
/*
173
 * return the key type of a JWT
174
 */
175
5.33k
int oidc_jwt_alg2kty(const oidc_jwt_t *jwt) {
176
5.33k
  return oidc_alg2kty(jwt->header.alg);
177
5.33k
}
178
179
/*
180
 * return the key size for an algorithm
181
 */
182
4.71k
unsigned int oidc_alg2keysize(const char *alg) {
183
184
4.71k
  if (alg == NULL)
185
2.32k
    return 0;
186
187
2.39k
  if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0)
188
15
    return 16;
189
2.37k
  if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0)
190
2
    return 24;
191
2.37k
  if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)
192
234
    return 32;
193
194
2.13k
  if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) ||
195
2.13k
      (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0))
196
1.20k
    return 32;
197
930
  if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) ||
198
926
      (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS384) == 0))
199
7
    return 48;
200
923
  if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) ||
201
919
      (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS512) == 0))
202
7
    return 64;
203
204
916
  return 0;
205
923
}
206
207
4.49k
#define OIDC_JOSE_JWT_ISS "iss"
208
4.49k
#define OIDC_JOSE_JWT_SUB "sub"
209
4.49k
#define OIDC_JOSE_JWT_EXP "exp"
210
4.49k
#define OIDC_JOSE_JWT_IAT "iat"
211
212
/*
213
 * parse JWT payload
214
 */
215
static apr_byte_t oidc_jose_parse_payload(apr_pool_t *pool, const char *s_payload, size_t s_payload_len,
216
4.58k
            oidc_jwt_payload_t *payload, oidc_jose_error_t *err) {
217
218
  /* decode the string in to a JSON structure into value->json */
219
4.58k
  json_error_t json_error;
220
4.58k
  payload->value.str = apr_pstrndup(pool, s_payload, s_payload_len);
221
4.58k
  payload->value.json = json_loads(payload->value.str, 0, &json_error);
222
223
  /* check that we've actually got a JSON value back */
224
4.58k
  if (payload->value.json == NULL) {
225
81
    oidc_jose_error(err, "JSON parsing (json_loads) failed: %s (%s)", json_error.text, payload->value.str);
226
81
    return FALSE;
227
81
  }
228
229
  /* check that the value is a JSON object */
230
4.50k
  if (!json_is_object(payload->value.json)) {
231
3
    oidc_jose_error(err, "JSON value is not an object");
232
3
    return FALSE;
233
3
  }
234
235
  /* get the (optional) "iss" value from the JSON payload */
236
4.49k
  oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_ISS, FALSE, &payload->iss, NULL);
237
238
  /* get the (optional) "exp" value from the JSON payload */
239
4.49k
  oidc_jose_get_timestamp(payload->value.json, OIDC_JOSE_JWT_EXP, FALSE, &payload->exp, NULL);
240
241
  /* get the (optional) "iat" value from the JSON payload */
242
4.49k
  oidc_jose_get_timestamp(payload->value.json, OIDC_JOSE_JWT_IAT, FALSE, &payload->iat, NULL);
243
244
  /* get the (optional) "sub" value from the JSON payload */
245
4.49k
  oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_SUB, FALSE, &payload->sub, NULL);
246
247
4.49k
  return TRUE;
248
4.50k
}
249
250
/*
251
 * parse and (optionally) decrypt a JSON Web Token
252
 */
253
apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *input_json, oidc_jwt_t **j_jwt, apr_hash_t *keys,
254
9.59k
        apr_byte_t compress, oidc_jose_error_t *err) {
255
256
9.59k
  cjose_err cjose_err;
257
9.59k
  char *s_json = NULL;
258
259
9.59k
  if (oidc_jwe_decrypt(pool, input_json, keys, &s_json, NULL, err, FALSE) == FALSE)
260
315
    return FALSE;
261
262
9.28k
  *j_jwt = oidc_jwt_new(pool, FALSE, FALSE);
263
9.28k
  if (*j_jwt == NULL)
264
0
    return FALSE;
265
9.28k
  oidc_jwt_t *jwt = *j_jwt;
266
267
9.28k
  jwt->cjose_jws = cjose_jws_import(s_json, _oidc_strlen(s_json), &cjose_err);
268
9.28k
  if (jwt->cjose_jws == NULL) {
269
4.70k
    oidc_jose_error(err, "cjose_jws_import failed: %s", oidc_cjose_e2s(pool, cjose_err));
270
4.70k
    oidc_jwt_destroy(jwt);
271
4.70k
    *j_jwt = NULL;
272
4.70k
    return FALSE;
273
4.70k
  }
274
275
4.58k
  cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws);
276
4.58k
  jwt->header.value.json = json_copy((json_t *)hdr);
277
4.58k
  jwt->header.value.str =
278
4.58k
      oidc_json_encode(pool, jwt->header.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT);
279
280
4.58k
  jwt->header.alg = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err));
281
4.58k
  jwt->header.enc = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ENC, &cjose_err));
282
4.58k
  jwt->header.kid = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err));
283
284
4.58k
  uint8_t *plaintext = NULL;
285
4.58k
  size_t plaintext_len = 0;
286
4.58k
  if (cjose_jws_get_plaintext(jwt->cjose_jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) {
287
0
    oidc_jwt_destroy(jwt);
288
0
    *j_jwt = NULL;
289
0
    oidc_jose_error(err, "cjose_jws_get_plaintext failed: %s", oidc_cjose_e2s(pool, cjose_err));
290
0
    return FALSE;
291
0
  }
292
293
4.58k
  if (compress == TRUE) {
294
0
    char *payload = NULL;
295
0
    int payload_len = 0;
296
0
    if (oidc_jose_uncompress(pool, (char *)plaintext, (int)plaintext_len, &payload, &payload_len, err) ==
297
0
        FALSE) {
298
0
      oidc_jwt_destroy(jwt);
299
0
      *j_jwt = NULL;
300
0
      return FALSE;
301
0
    }
302
0
    plaintext = (uint8_t *)payload;
303
0
    plaintext_len = payload_len;
304
0
  }
305
306
4.58k
  if (oidc_jose_parse_payload(pool, (const char *)plaintext, plaintext_len, &jwt->payload, err) == FALSE) {
307
84
    oidc_jwt_destroy(jwt);
308
84
    *j_jwt = NULL;
309
84
    return FALSE;
310
84
  }
311
312
4.49k
  return TRUE;
313
4.58k
}
314
315
/*
316
 * destroy resources allocated for JWT
317
 */
318
22.3k
void oidc_jwt_destroy(oidc_jwt_t *jwt) {
319
22.3k
  if (jwt) {
320
21.7k
    if (jwt->header.value.json) {
321
17.0k
      json_decref(jwt->header.value.json);
322
17.0k
      jwt->header.value.json = NULL;
323
17.0k
      jwt->header.value.str = NULL;
324
17.0k
    }
325
21.7k
    if (jwt->payload.value.json) {
326
9.04k
      json_decref(jwt->payload.value.json);
327
9.04k
      jwt->payload.value.json = NULL;
328
9.04k
      jwt->payload.value.str = NULL;
329
9.04k
    }
330
21.7k
    if (jwt->cjose_jws) {
331
9.12k
      cjose_jws_release(jwt->cjose_jws);
332
9.12k
      jwt->cjose_jws = NULL;
333
9.12k
    }
334
21.7k
  }
335
22.3k
}
336
337
/*
338
 * sign a JWT
339
 */
340
apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, const oidc_jwk_t *jwk, apr_byte_t compress,
341
4.54k
       oidc_jose_error_t *err) {
342
343
4.54k
  cjose_header_t *hdr = (cjose_header_t *)jwt->header.value.json;
344
345
4.54k
  if (jwt->header.alg)
346
4.54k
    oidc_jwt_hdr_set(jwt, CJOSE_HDR_ALG, jwt->header.alg);
347
4.54k
  if (jwt->header.kid)
348
0
    oidc_jwt_hdr_set(jwt, CJOSE_HDR_KID, jwt->header.kid);
349
4.54k
  if (jwt->header.enc)
350
0
    oidc_jwt_hdr_set(jwt, CJOSE_HDR_ENC, jwt->header.enc);
351
4.54k
  if (jwt->header.x5t)
352
0
    oidc_jwt_hdr_set(jwt, OIDC_JOSE_JWK_X5T_STR, jwt->header.x5t);
353
354
4.54k
  if (jwt->cjose_jws)
355
0
    cjose_jws_release(jwt->cjose_jws);
356
357
4.54k
  cjose_err cjose_err;
358
4.54k
  char *plaintext = oidc_json_encode(pool, jwt->payload.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT);
359
360
4.54k
  char *s_payload = NULL;
361
4.54k
  int payload_len = 0;
362
4.54k
  if (compress == TRUE) {
363
0
    if (oidc_jose_compress(pool, plaintext, (int)_oidc_strlen(plaintext), &s_payload, &payload_len, err) ==
364
0
        FALSE) {
365
0
      return FALSE;
366
0
    }
367
4.54k
  } else {
368
4.54k
    s_payload = plaintext;
369
4.54k
    payload_len = (int)_oidc_strlen(plaintext);
370
4.54k
    jwt->payload.value.str = plaintext;
371
4.54k
  }
372
373
4.54k
  jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr, (const uint8_t *)s_payload, payload_len, &cjose_err);
374
375
4.54k
  if (jwt->cjose_jws == NULL) {
376
0
    oidc_jose_error(err, "cjose_jws_sign failed: %s", oidc_cjose_e2s(pool, cjose_err));
377
0
    return FALSE;
378
0
  }
379
380
4.54k
  return TRUE;
381
4.54k
}
382
383
/*
384
 * verify the signature of a JWT
385
 */
386
/*
387
 * verify a JWS against a single JWK, reporting failure into err and clearing
388
 * jwt->cjose_jws when the linked cjose version is known to leave it in an
389
 * unusable state after a failed verify
390
 */
391
static apr_byte_t oidc_jwt_verify_with_key(apr_pool_t *pool, oidc_jwt_t *jwt, const oidc_jwk_t *jwk,
392
4.25k
             oidc_jose_error_t *err) {
393
4.25k
  cjose_err cjose_err;
394
4.25k
  apr_byte_t rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err);
395
4.25k
  if (rc == FALSE) {
396
385
    oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err));
397
385
    if (oidc_jose_version_deprecated(pool))
398
0
      jwt->cjose_jws = NULL;
399
385
  }
400
4.25k
  return rc;
401
4.25k
}
402
403
/*
404
 * verify a JWS by iterating over all configured keys with a matching kty
405
 */
406
4.22k
static apr_byte_t oidc_jwt_verify_any(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) {
407
4.22k
  oidc_jwk_t *jwk = NULL;
408
4.22k
  apr_byte_t rc = FALSE;
409
410
5.68k
  for (apr_hash_index_t *hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) {
411
5.32k
    apr_hash_this(hi, NULL, NULL, (void **)&jwk);
412
5.32k
    if (jwk->kty == oidc_jwt_alg2kty(jwt))
413
4.24k
      rc = oidc_jwt_verify_with_key(pool, jwt, jwk, err);
414
5.32k
    if ((rc == TRUE) || (jwt->cjose_jws == NULL))
415
3.86k
      break;
416
5.32k
  }
417
418
4.22k
  if (rc == FALSE)
419
362
    oidc_jose_error(
420
4.22k
        err, "could not verify signature against any of the (%d) provided keys%s", apr_hash_count(keys),
421
4.22k
        apr_hash_count(keys) > 0
422
4.22k
      ? ""
423
4.22k
      : apr_psprintf(pool,
424
4.22k
               "; you have probably provided no or incorrect keys/key-types for algorithm: %s",
425
4.22k
               jwt->header.alg));
426
427
4.22k
  return rc;
428
4.22k
}
429
430
4.23k
apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) {
431
432
4.23k
  if (jwt->header.kid == NULL)
433
4.22k
    return oidc_jwt_verify_any(pool, jwt, keys, err);
434
435
11
  const oidc_jwk_t *jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING);
436
11
  if (jwk == NULL) {
437
5
    oidc_jose_error(err, "could not find key with kid: %s", jwt->header.kid);
438
5
    return FALSE;
439
5
  }
440
441
  /* make sure the key type is compatible with the algorithm, just as the no-kid path does, rather than
442
   * relying solely on cjose to reject a mismatch (defense in depth against key/algorithm confusion) */
443
6
  if (jwk->kty != oidc_jwt_alg2kty(jwt)) {
444
4
    oidc_jose_error(err, "key type of key with kid \"%s\" does not match the JWT \"alg\" header \"%s\"",
445
4
        jwt->header.kid, jwt->header.alg);
446
4
    return FALSE;
447
4
  }
448
449
2
  return oidc_jwt_verify_with_key(pool, jwt, jwk, err);
450
6
}