Coverage Report

Created: 2026-09-27 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/src/util/jwt.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
 * All rights reserved.
23
 *
24
 * DISCLAIMER OF WARRANTIES:
25
 *
26
 * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT
27
 * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING,
28
 * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT,
29
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  NOR ARE THERE ANY
30
 * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE
31
 * USAGE.  FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET
32
 * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE
33
 * WILL BE UNINTERRUPTED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
34
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
35
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF
36
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
37
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
 *
40
 * @Author: Hans Zandbelt - hans.zandbelt@openidc.com
41
 */
42
43
#include "proto/proto.h"
44
#include "util/util.h"
45
#include "util/util_cfg.h"
46
47
7.89k
#define OIDC_JWT_INTERNAL_NO_COMPRESS_ENV_VAR "OIDC_JWT_INTERNAL_NO_COMPRESS"
48
49
/*
50
 * helper function to override a variable value with an optionally provided environment variable
51
 */
52
static apr_byte_t oidc_util_env_var_override(const request_rec *r, const char *env_var_name,
53
7.89k
               apr_byte_t return_when_set) {
54
7.89k
  const char *s = NULL;
55
7.89k
  if (r->subprocess_env == NULL)
56
0
    return !return_when_set;
57
7.89k
  s = apr_table_get(r->subprocess_env, env_var_name);
58
7.89k
  return (s != NULL) && (_oidc_strcmp(s, "true") == 0) ? return_when_set : !return_when_set;
59
7.89k
}
60
61
/*
62
 * check if we need to compress (internal) encrypted JWTs or not
63
 */
64
7.89k
static apr_byte_t oidc_util_jwt_internal_compress(const request_rec *r) {
65
  // avoid compressing JWTs that need to be compatible with external producers/consumers
66
7.89k
  return oidc_util_env_var_override(r, OIDC_JWT_INTERNAL_NO_COMPRESS_ENV_VAR, FALSE);
67
7.89k
}
68
69
/*
70
 * wrap the precomputed PBKDF2-derived key material for a crypto passphrase into a JWK; the
71
 * expensive stretching itself happens once, at post_config time (see
72
 * oidc_cfg_crypto_passphrase_derive_keys()), not on every request
73
 */
74
static apr_byte_t oidc_util_jwt_key_from_derived(request_rec *r, const unsigned char *derived_key,
75
24.8k
             apr_byte_t derived_key_set, oidc_jwk_t **jwk) {
76
24.8k
  oidc_jose_error_t err;
77
24.8k
  if (derived_key_set == FALSE) {
78
0
    oidc_error(r, "no derived key material available for the configured passphrase");
79
0
    return FALSE;
80
0
  }
81
24.8k
  *jwk = oidc_jwk_create_symmetric_key(r->pool, NULL, derived_key, OIDC_CRYPTO_PASSPHRASE_DERIVED_KEY_LEN, FALSE,
82
24.8k
               &err);
83
24.8k
  if (*jwk == NULL) {
84
0
    oidc_error(r, "oidc_jwk_create_symmetric_key failed: %s", oidc_jose_e2s(r->pool, err));
85
0
    return FALSE;
86
0
  }
87
24.8k
  return TRUE;
88
24.8k
}
89
90
/*
91
 * create an encrypted JWT for internal purposes (i.e. state cookie, session cookie, or encrypted cache value)
92
 */
93
apr_byte_t oidc_util_jwt_create(request_rec *r, const oidc_crypto_passphrase_t *passphrase, const char *s_payload,
94
7.89k
        char **compact_encoded_jwt) {
95
96
7.89k
  apr_byte_t rv = FALSE;
97
7.89k
  oidc_jose_error_t err;
98
7.89k
  char *cser = NULL;
99
7.89k
  int cser_len = 0;
100
101
7.89k
  oidc_jwk_t *jwk = NULL;
102
7.89k
  oidc_jwt_t *jwe = NULL;
103
104
7.89k
  if (passphrase->secret1 == NULL) {
105
0
    oidc_error(r, "secret is not set");
106
0
    goto end;
107
0
  }
108
109
7.89k
  if (oidc_util_jwt_key_from_derived(r, passphrase->derived_key1, passphrase->derived_key1_set, &jwk) == FALSE)
110
0
    goto end;
111
112
7.89k
  if (oidc_util_jwt_internal_compress(r)) {
113
7.89k
    if (oidc_jose_compress(r->pool, s_payload, (int)_oidc_strlen(s_payload), &cser, &cser_len, &err) ==
114
7.89k
        FALSE) {
115
0
      oidc_error(r, "oidc_jose_compress failed: %s", oidc_jose_e2s(r->pool, err));
116
0
      goto end;
117
0
    }
118
7.89k
  } else {
119
0
    cser = apr_pstrdup(r->pool, s_payload);
120
0
    cser_len = (int)_oidc_strlen(s_payload);
121
0
  }
122
123
7.89k
  jwe = oidc_jwt_new(r->pool, TRUE, FALSE);
124
7.89k
  if (jwe == NULL) {
125
0
    oidc_error(r, "creating JWE failed");
126
0
    goto end;
127
0
  }
128
129
7.89k
  jwe->header.alg = apr_pstrdup(r->pool, OIDC_JOSE_HDR_ALG_DIR);
130
7.89k
  jwe->header.enc = apr_pstrdup(r->pool, OIDC_JOSE_HDR_ENC_A256GCM);
131
7.89k
  if (passphrase->secret2 != NULL)
132
0
    jwe->header.kid = apr_pstrdup(r->pool, "1");
133
134
7.89k
  if (oidc_jwt_encrypt(r->pool, jwe, jwk, cser, cser_len, compact_encoded_jwt, &err) == FALSE) {
135
0
    oidc_error(r, "encrypting JWT failed: %s", oidc_jose_e2s(r->pool, err));
136
0
    goto end;
137
0
  }
138
139
7.89k
  rv = TRUE;
140
141
7.89k
end:
142
143
7.89k
  if (jwe != NULL)
144
7.89k
    oidc_jwt_destroy(jwe);
145
7.89k
  if (jwk != NULL)
146
7.89k
    oidc_jwk_destroy(jwk);
147
148
7.89k
  return rv;
149
7.89k
}
150
151
/*
152
 * verify an encrypted JWT for internal purposes
153
 */
154
apr_byte_t oidc_util_jwt_verify(request_rec *r, const oidc_crypto_passphrase_t *passphrase,
155
26.3k
        const char *compact_encoded_jwt, char **s_payload) {
156
157
26.3k
  apr_byte_t rv = FALSE;
158
26.3k
  oidc_jose_error_t err;
159
26.3k
  oidc_jwk_t *jwk = NULL;
160
26.3k
  char *payload = NULL;
161
26.3k
  int payload_len = 0;
162
26.3k
  char *plaintext = NULL;
163
26.3k
  int plaintext_len = 0;
164
26.3k
  apr_hash_t *keys = NULL;
165
26.3k
  char *alg = NULL;
166
26.3k
  char *enc = NULL;
167
26.3k
  char *kid = NULL;
168
169
26.3k
  oidc_proto_jwt_header_peek(r, compact_encoded_jwt, &alg, &enc, &kid);
170
26.3k
  if ((_oidc_strcmp(alg, OIDC_JOSE_HDR_ALG_DIR) != 0) || (_oidc_strcmp(enc, OIDC_JOSE_HDR_ENC_A256GCM) != 0)) {
171
9.35k
    oidc_error(r, "corrupted JWE header, alg=\"%s\" enc=\"%s\"", alg, enc);
172
9.35k
    goto end;
173
9.35k
  }
174
175
16.9k
  keys = apr_hash_make(r->pool);
176
177
16.9k
  if ((passphrase->secret2 != NULL) && (kid == NULL)) {
178
0
    if (oidc_util_jwt_key_from_derived(r, passphrase->derived_key2, passphrase->derived_key2_set, &jwk) ==
179
0
        FALSE)
180
0
      goto end;
181
16.9k
  } else {
182
16.9k
    if (oidc_util_jwt_key_from_derived(r, passphrase->derived_key1, passphrase->derived_key1_set, &jwk) ==
183
16.9k
        FALSE)
184
0
      goto end;
185
16.9k
  }
186
16.9k
  apr_hash_set(keys, "1", APR_HASH_KEY_STRING, jwk);
187
188
  /* Require JWE import so a matching header cannot make unauthenticated input pass verification. */
189
16.9k
  if (oidc_jwe_decrypt(r->pool, compact_encoded_jwt, keys, &plaintext, &plaintext_len, &err, TRUE) == FALSE) {
190
12.5k
    oidc_error(r, "decrypting JWE failed: %s", oidc_jose_e2s(r->pool, err));
191
12.5k
    goto end;
192
12.5k
  }
193
194
  /* Always detect on read; OIDC_JWT_INTERNAL_NO_COMPRESS controls only future writes. */
195
4.42k
  if (oidc_jose_uncompress(r->pool, plaintext, plaintext_len, &payload, &payload_len, &err) == FALSE) {
196
0
    oidc_error(r, "oidc_jose_uncompress failed: %s", oidc_jose_e2s(r->pool, err));
197
0
    goto end;
198
0
  }
199
200
4.42k
  *s_payload = apr_pstrndup(r->pool, payload, payload_len);
201
202
4.42k
  rv = TRUE;
203
204
26.3k
end:
205
206
26.3k
  if (jwk != NULL)
207
16.9k
    oidc_jwk_destroy(jwk);
208
209
26.3k
  return rv;
210
4.42k
}