Coverage Report

Created: 2026-09-27 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mod_auth_openidc/test/util.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
44
#include "util.h"
45
#include "cfg/cfg_int.h"
46
#include "cfg/dir.h"
47
#include "handle/handle.h"
48
#include "metadata.h"
49
#include "proto/proto.h"
50
#include "session.h"
51
#include "util/util.h"
52
#include <apr_env.h>
53
#include <apr_file_info.h>
54
#include <openssl/evp.h>
55
56
/* Per-test fixture state; module-level test statics need their own CK_FORK=no reset. */
57
static apr_pool_t *pool = NULL;
58
static request_rec *request = NULL;
59
60
/* Cache PBKDF2 by secret across tests; direct KDF tests bypass this optimization. */
61
2
#define OIDC_TEST_KDF_CACHE_MAX 32
62
static struct {
63
  char secret[128];
64
  unsigned char key[OIDC_CRYPTO_PASSPHRASE_DERIVED_KEY_LEN];
65
} oidc_test_kdf_cache[OIDC_TEST_KDF_CACHE_MAX];
66
static int oidc_test_kdf_cache_n = 0;
67
68
2
static apr_byte_t oidc_test_key_derive_cached(const char *secret, unsigned char *out) {
69
2
  int i;
70
2
  for (i = 0; i < oidc_test_kdf_cache_n; i++) {
71
0
    if (_oidc_strcmp(oidc_test_kdf_cache[i].secret, secret) == 0) {
72
0
      _oidc_memcpy(out, oidc_test_kdf_cache[i].key, OIDC_CRYPTO_PASSPHRASE_DERIVED_KEY_LEN);
73
0
      return TRUE;
74
0
    }
75
0
  }
76
2
  if (oidc_util_key_derive_passphrase_key(secret, out, OIDC_CRYPTO_PASSPHRASE_DERIVED_KEY_LEN) == FALSE)
77
0
    return FALSE;
78
2
  if ((oidc_test_kdf_cache_n < OIDC_TEST_KDF_CACHE_MAX) &&
79
2
      (_oidc_strlen(secret) < sizeof(oidc_test_kdf_cache[0].secret))) {
80
2
    _oidc_strcpy(oidc_test_kdf_cache[oidc_test_kdf_cache_n].secret, secret);
81
2
    _oidc_memcpy(oidc_test_kdf_cache[oidc_test_kdf_cache_n].key, out,
82
2
           OIDC_CRYPTO_PASSPHRASE_DERIVED_KEY_LEN);
83
2
    oidc_test_kdf_cache_n++;
84
2
  }
85
2
  return TRUE;
86
2
}
87
88
/*
89
 * test-only drop-in for oidc_cfg_crypto_passphrase_derive_keys()/oidc_crypto_passphrase_derive_keys()
90
 * that routes the actual KDF work through the cache above; same semantics (skips a slot whose
91
 * *_set flag is already TRUE, treats an empty secret as "not configured").
92
 */
93
2
static apr_byte_t oidc_test_crypto_passphrase_derive_keys_cached(oidc_crypto_passphrase_t *cp) {
94
2
  if ((cp->secret1 != NULL) && (_oidc_strlen(cp->secret1) > 0) && (cp->derived_key1_set == FALSE)) {
95
2
    if (oidc_test_key_derive_cached(cp->secret1, cp->derived_key1) == FALSE)
96
0
      return FALSE;
97
2
    cp->derived_key1_set = TRUE;
98
2
  }
99
2
  if ((cp->secret2 != NULL) && (_oidc_strlen(cp->secret2) > 0) && (cp->derived_key2_set == FALSE)) {
100
0
    if (oidc_test_key_derive_cached(cp->secret2, cp->derived_key2) == FALSE)
101
0
      return FALSE;
102
0
    cp->derived_key2_set = TRUE;
103
0
  }
104
2
  return TRUE;
105
2
}
106
107
2
static request_rec *oidc_test_request_init(apr_pool_t *pool) {
108
2
  const unsigned int kIdx = 0;
109
2
  const unsigned int kEls = kIdx + 1;
110
2
  request_rec *request = (request_rec *)apr_pcalloc(pool, sizeof(request_rec));
111
112
2
  apr_pool_create(&request->pool, pool);
113
114
2
  request->subprocess_env = apr_table_make(request->pool, 0);
115
2
  request->notes = apr_table_make(request->pool, 0);
116
117
2
  request->headers_in = apr_table_make(request->pool, 0);
118
2
  request->headers_out = apr_table_make(request->pool, 0);
119
2
  request->err_headers_out = apr_table_make(request->pool, 0);
120
121
2
  apr_table_set(request->headers_in, "Host", "www.example.com");
122
2
  apr_table_set(request->headers_in, "OIDC_foo", "some-value");
123
2
  apr_table_set(request->headers_in, "Cookie",
124
2
          "foo=bar; "
125
2
          "mod_auth_openidc_session"
126
2
          "=0123456789abcdef; baz=zot");
127
128
2
  request->server = apr_pcalloc(pool, sizeof(struct server_rec));
129
2
  request->server->process = apr_pcalloc(pool, sizeof(struct process_rec));
130
2
  apr_pool_create(&request->server->process->pool, pool);
131
2
  apr_pool_create(&request->server->process->pconf, pool);
132
2
  request->connection = apr_pcalloc(pool, sizeof(struct conn_rec));
133
2
  request->connection->bucket_alloc = apr_bucket_alloc_create(pool);
134
2
  request->connection->local_addr = apr_pcalloc(pool, sizeof(apr_sockaddr_t));
135
  /* minimal output filter carrying the request so the ap_pass_brigade stub
136
   * can capture sent response bodies into the "sent_body" request state */
137
2
  request->output_filters = apr_pcalloc(pool, sizeof(ap_filter_t));
138
2
  request->output_filters->r = request;
139
140
2
  apr_pool_userdata_set("https", "scheme", NULL, request->pool);
141
2
  request->server->server_hostname = "www.example.com";
142
2
  request->connection->local_addr->port = 4433;
143
2
  request->unparsed_uri = "/bla?foo=bar&param1=value1";
144
2
  request->args = "foo=bar&param1=value1";
145
2
  apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar&param1=value1", &request->parsed_uri);
146
147
2
  auth_openidc_module.module_index = kIdx;
148
2
  oidc_cfg_t *cfg = oidc_cfg_server_create(request->server->process->pconf, request->server);
149
150
2
  oidc_cfg_provider_issuer_set(request->server->process->pconf, oidc_cfg_provider_get(cfg),
151
2
             "https://idp.example.com");
152
2
  oidc_cfg_provider_authorization_endpoint_url_set(request->server->process->pconf, oidc_cfg_provider_get(cfg),
153
2
               "https://idp.example.com/authorize");
154
2
  oidc_cfg_provider_client_id_set(request->server->process->pconf, oidc_cfg_provider_get(cfg), "client_id");
155
156
2
  cfg->redirect_uri = "https://www.example.com/protected/";
157
158
2
  oidc_dir_cfg_t *d_cfg = oidc_cfg_dir_config_create(request->server->process->pconf, NULL);
159
160
  // coverity[suspicious_sizeof]
161
2
  request->server->module_config = apr_pcalloc(request->server->process->pconf, sizeof(void *) * kEls);
162
  // coverity[suspicious_sizeof]
163
2
  request->per_dir_config = apr_pcalloc(request->server->process->pconf, sizeof(void *) * kEls);
164
2
  ap_set_module_config(request->server->module_config, &auth_openidc_module, cfg);
165
2
  ap_set_module_config(request->per_dir_config, &auth_openidc_module, d_cfg);
166
167
  // TODO:
168
2
  cfg->public_keys = apr_array_make(request->server->process->pconf, 1, sizeof(const char *));
169
2
  cfg->private_keys = apr_array_make(request->server->process->pconf, 1, sizeof(const char *));
170
171
2
  cfg->crypto_passphrase.secret1 = "12345678901234567890123456789012";
172
2
  if (oidc_test_crypto_passphrase_derive_keys_cached(&cfg->crypto_passphrase) == FALSE) {
173
0
    fprintf(stderr, "oidc_cfg_crypto_passphrase_derive_keys failed!\n");
174
0
    exit(-1);
175
0
  }
176
2
  cfg->cache.impl = &oidc_cache_shm;
177
2
  cfg->cache.cfg = NULL;
178
2
  cfg->cache.shm_size_max = 500;
179
2
  const char *shm_size = getenv("OIDC_TEST_SHM_SIZE");
180
2
  if (shm_size != NULL) {
181
0
    char *end = NULL;
182
0
    long parsed = strtol(shm_size, &end, 10);
183
0
    if ((end != shm_size) && (*end == '\0') && (parsed >= 128) && (parsed <= 1000000))
184
0
      cfg->cache.shm_size_max = (int)parsed;
185
0
  }
186
2
  cfg->cache.shm_entry_size_max = 16384 + 255 + 17;
187
2
  cfg->cache.encrypt = 1;
188
  /* full post-config so the cache backend AND the shared refresh-grant mutex get set up */
189
2
  if (oidc_cfg_post_config(request->server->process->pconf, cfg, request->server) != OK) {
190
0
    fprintf(stderr, "oidc_cfg_post_config failed!\n");
191
0
    exit(-1);
192
0
  }
193
194
2
  if (oidc_cfg_dir_post_config(request->server) != OK) {
195
0
    fprintf(stderr, "oidc_cfg_dir_post_config failed!\n");
196
0
    exit(-1);
197
0
  }
198
199
2
  oidc_http_curl_pool_init(request->server->process->pconf);
200
201
2
  return request;
202
2
}
203
204
2
void oidc_test_setup(void) {
205
2
  apr_initialize();
206
2
  oidc_pre_config_init();
207
  /* reset the stubbed AuthType so a test that changed it does not leak into
208
   * the next one under CK_FORK=no */
209
2
  oidc_test_set_auth_type(NULL);
210
2
  apr_pool_create(&pool, NULL);
211
2
  request = oidc_test_request_init(pool);
212
2
}
213
214
0
void oidc_test_teardown(void) {
215
  /* release the process-wide refresh mutex before apr_terminate frees its pool */
216
0
  if (request != NULL) {
217
0
    oidc_cfg_t *cfg = oidc_test_cfg_get();
218
0
    oidc_cfg_process_cleanup(cfg, request->server);
219
0
  }
220
0
  EVP_cleanup();
221
0
  apr_terminate();
222
0
  request = NULL;
223
0
  pool = NULL;
224
0
}
225
226
0
char *oidc_test_mkdtemp(apr_pool_t *pool, const char *prefix) {
227
0
  static int counter = 0;
228
0
  const char *dir = NULL;
229
0
  char *path = NULL;
230
0
  int i;
231
232
0
  if (apr_temp_dir_get(&dir, pool) != APR_SUCCESS)
233
0
    return NULL;
234
235
  /* the time in microseconds plus a counter is unique enough for a test run; retry the odd
236
   * collision with a run that left its directory behind */
237
0
  for (i = 0; i < 16; i++) {
238
0
    path = apr_psprintf(pool, "%s/%s.%" APR_TIME_T_FMT ".%d", dir, prefix, apr_time_now(), counter++);
239
0
    if (apr_dir_make(path, APR_FPROT_OS_DEFAULT, pool) == APR_SUCCESS)
240
0
      return path;
241
0
  }
242
243
0
  return NULL;
244
0
}
245
246
0
const char *oidc_test_srcdir(void) {
247
0
  static char dir[1024];
248
0
  const char *env = getenv("srcdir");
249
0
  char *p = NULL;
250
251
0
  apr_cpystrn(dir, (env != NULL) ? env : ".", sizeof(dir));
252
0
  for (p = dir; *p != '\0'; p++)
253
0
    if (*p == '\\')
254
0
      *p = '/';
255
256
0
  return dir;
257
0
}
258
259
0
void oidc_test_setenv(apr_pool_t *pool, const char *name, const char *value) {
260
#ifdef _WIN32
261
  /* apr_env_set goes through SetEnvironmentVariable, which the C runtime's getenv -- what the
262
   * module reads -- does not see; _putenv_s updates both */
263
  _putenv_s(name, value);
264
#else
265
0
  apr_env_set(name, value, pool);
266
0
#endif
267
0
}
268
269
0
void oidc_test_unsetenv(apr_pool_t *pool, const char *name) {
270
#ifdef _WIN32
271
  _putenv_s(name, "");
272
#else
273
0
  apr_env_delete(name, pool);
274
0
#endif
275
0
}
276
277
1.78k
apr_pool_t *oidc_test_pool_get(void) {
278
1.78k
  return pool;
279
1.78k
}
280
281
1.78k
request_rec *oidc_test_request_get(void) {
282
1.78k
  return request;
283
1.78k
}
284
285
1.78k
oidc_cfg_t *oidc_test_cfg_get(void) {
286
1.78k
  return (oidc_cfg_t *)ap_get_module_config(request->server->module_config, &auth_openidc_module);
287
1.78k
}
288
289
0
cmd_parms *oidc_test_cmd_get(const char *primitive) {
290
0
  request_rec *r = oidc_test_request_get();
291
0
  cmd_parms *cmd = apr_pcalloc(r->pool, sizeof(cmd_parms));
292
0
  cmd->server = r->server;
293
0
  cmd->pool = r->pool;
294
0
  cmd->temp_pool = r->pool;
295
0
  cmd->directive = apr_pcalloc(cmd->pool, sizeof(ap_directive_t));
296
0
  cmd->directive->directive = primitive;
297
0
  return cmd;
298
0
}
299
300
/* Re-derive keys after tests directly replace passphrase secrets at runtime. */
301
0
void oidc_test_crypto_passphrase_rederive(oidc_cfg_t *cfg) {
302
0
  cfg->crypto_passphrase.derived_key1_set = FALSE;
303
0
  cfg->crypto_passphrase.derived_key2_set = FALSE;
304
0
  if (oidc_test_crypto_passphrase_derive_keys_cached(&cfg->crypto_passphrase) == FALSE) {
305
    fprintf(stderr, "oidc_cfg_crypto_passphrase_derive_keys failed!\n");
306
0
    exit(-1);
307
0
  }
308
0
}