/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¶m1=value1"; |
144 | 2 | request->args = "foo=bar¶m1=value1"; |
145 | 2 | apr_uri_parse(request->pool, "https://www.example.com/bla?foo=bar¶m1=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 | 77 | apr_pool_t *oidc_test_pool_get(void) { |
278 | 77 | return pool; |
279 | 77 | } |
280 | | |
281 | 0 | request_rec *oidc_test_request_get(void) { |
282 | 0 | return request; |
283 | 0 | } |
284 | | |
285 | 0 | oidc_cfg_t *oidc_test_cfg_get(void) { |
286 | 0 | return (oidc_cfg_t *)ap_get_module_config(request->server->module_config, &auth_openidc_module); |
287 | 0 | } |
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 | } |