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, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include "common.h"
20 #include "config.h"
21 #include "timing.h"
22 #include "jwt.h"
23
24 #include <cjose/cjose.h>
25 #include <jansson.h>
26
27 #include <string.h>
28 #include <search.h>
29 #include <errno.h>
30
31 #define JSONError(err) PluginError("json-err: %s:%d:%d: %s", (err).source, (err).line, (err).column, (err).text)
32
33 #define AUTH_DENY 0
34 #define AUTH_ALLOW 1
35 struct auth_directive {
36 char auth;
37 char *container;
38 };
39
40 struct config {
41 struct hsearch_data *issuers;
42 cjose_jwk_t ***jwkis;
43 char **issuer_names;
44 struct signer signer;
45 struct auth_directive *auth_directives;
46 char *id;
47 bool strip_token;
48 };
49
50 cjose_jwk_t **
find_keys(struct config * cfg,const char * issuer)51 find_keys(struct config *cfg, const char *issuer)
52 {
53 ENTRY *entry;
54 if (!hsearch_r((ENTRY){.key = (char *)issuer}, FIND, &entry, cfg->issuers) || !entry) {
55 PluginDebug("Unable to locate any keys at %p for issuer %s in %p->%p", entry, issuer, cfg, cfg->issuers);
56 return NULL;
57 }
58
59 int n = 0;
60 for (cjose_jwk_t **jwks = entry->data; *jwks; ++jwks, ++n) {
61 ;
62 }
63 PluginDebug("Located %d keys for issuer %s in %p->%p", n, issuer, cfg, cfg->issuers);
64 return entry->data;
65 }
66
67 cjose_jwk_t *
find_key_by_kid(struct config * cfg,const char * issuer,const char * kid)68 find_key_by_kid(struct config *cfg, const char *issuer, const char *kid)
69 {
70 const char *this_kid;
71 cjose_jwk_t **jwkis = find_keys(cfg, issuer);
72 if (!jwkis) {
73 return NULL;
74 }
75 for (cjose_jwk_t **jwks = jwkis; *jwks; ++jwks) {
76 if ((this_kid = cjose_jwk_get_kid(*jwks, NULL)) && !strcmp(this_kid, kid)) {
77 return *jwks;
78 }
79 }
80 return NULL;
81 }
82
83 const char *
config_get_id(struct config * cfg)84 config_get_id(struct config *cfg)
85 {
86 return cfg->id;
87 }
88
89 bool
config_strip_token(struct config * cfg)90 config_strip_token(struct config *cfg)
91 {
92 return cfg->strip_token;
93 }
94
95 struct config *
config_new(size_t n)96 config_new(size_t n)
97 {
98 PluginDebug("Creating new config object with size %ld", n);
99 struct config *cfg = malloc(sizeof *cfg);
100
101 cfg->issuers = calloc(1, sizeof *cfg->issuers);
102 if (!hcreate_r(n * 2, cfg->issuers)) {
103 PluginError("Unable to create config table (%d)!", errno);
104 free(cfg);
105 return NULL;
106 }
107 PluginDebug("Created table with size %d", cfg->issuers->size);
108
109 cfg->jwkis = malloc((n + 1) * sizeof *cfg->jwkis);
110 cfg->jwkis[n] = NULL;
111
112 cfg->issuer_names = malloc((n + 1) * sizeof *cfg->issuer_names);
113 cfg->issuer_names[n] = NULL;
114
115 cfg->signer.issuer = NULL;
116 cfg->signer.jwk = NULL;
117 cfg->signer.alg = NULL;
118
119 cfg->auth_directives = NULL;
120 cfg->id = NULL;
121
122 cfg->strip_token = false;
123
124 PluginDebug("New config object created at %p", cfg);
125 return cfg;
126 }
127
128 void
config_delete(struct config * cfg)129 config_delete(struct config *cfg)
130 {
131 if (!cfg) {
132 return;
133 }
134 hdestroy_r(cfg->issuers);
135 free(cfg->issuers);
136
137 for (cjose_jwk_t ***jwkis = cfg->jwkis; *jwkis; ++jwkis) {
138 for (cjose_jwk_t **jwks = *jwkis; *jwks; ++jwks) {
139 cjose_jwk_release(*jwks);
140 }
141 free(*jwkis);
142 }
143 free(cfg->jwkis);
144
145 if (cfg->id) {
146 free(cfg->id);
147 }
148
149 for (char **name = cfg->issuer_names; *name; ++name) {
150 free(*name);
151 }
152 free(cfg->issuer_names);
153
154 if (cfg->signer.alg) {
155 free(cfg->signer.alg);
156 }
157
158 if (cfg->auth_directives) {
159 for (struct auth_directive *ad = cfg->auth_directives; ad->container; ++ad) {
160 free(ad->container);
161 }
162 free(cfg->auth_directives);
163 }
164 free(cfg);
165 }
166
167 cjose_jwk_t *
load_jwk(json_t * obj,cjose_err * err)168 load_jwk(json_t *obj, cjose_err *err)
169 {
170 char *s = json_dumps(obj, JSON_COMPACT);
171 if (!s) {
172 PluginError("Failed to re-serialize JSON sub-object.");
173 return NULL;
174 }
175
176 cjose_jwk_t *jwk = cjose_jwk_import(s, strlen(s), err);
177 free(s);
178 return jwk;
179 }
180
181 static struct config *
read_config_from_json(json_t * const issuer_json)182 read_config_from_json(json_t *const issuer_json)
183 {
184 if (!json_is_object(issuer_json)) {
185 PluginError("Config file is not a valid JSON object");
186 goto issuer_fail;
187 }
188
189 size_t issuers_ct = json_object_size(issuer_json);
190 if (!issuers_ct) {
191 PluginError("Config file contains no issuers.");
192 goto issuer_fail;
193 }
194
195 struct config *cfg = config_new(issuers_ct);
196 if (!cfg) {
197 PluginError("Unable to allocate config.");
198 goto issuer_fail;
199 }
200
201 cjose_jwk_t ***jwkis = cfg->jwkis;
202 char **issuer = cfg->issuer_names;
203 const char *json_issuer;
204 json_t *jwks;
205 json_object_foreach(issuer_json, json_issuer, jwks)
206 {
207 *issuer = strdup(json_issuer);
208
209 json_t *ad_json = json_object_get(jwks, "auth_directives");
210 if (ad_json) {
211 PluginDebug("Loading auth_directives.");
212 size_t ad_ct = json_array_size(ad_json);
213 if (ad_ct) {
214 PluginDebug("Loading %d new auth_directives.", (int)ad_ct);
215 struct auth_directive *ad = cfg->auth_directives;
216 if (cfg->auth_directives) {
217 /* We've already got directives, so extend them. */
218 PluginDebug("Extending existing auth_directives.");
219 size_t ad_old_ct = 0;
220 while (ad->container) {
221 ++ad;
222 ++ad_old_ct;
223 }
224 cfg->auth_directives = realloc(cfg->auth_directives, (ad_ct + ad_old_ct + 1) * sizeof *cfg->auth_directives);
225 ad = cfg->auth_directives + ad_old_ct;
226 } else {
227 ad = cfg->auth_directives = malloc((ad_ct + 1) * sizeof *cfg->auth_directives);
228 }
229 json_t *ad_obj;
230 for (size_t idx = 0; (idx < ad_ct) && (ad_obj = json_array_get(ad_json, idx)); ++idx, ++ad) {
231 json_t *uri_json = json_object_get(ad_obj, "uri");
232 json_t *auth_json = json_object_get(ad_obj, "auth");
233 if (uri_json) {
234 const char *uri = json_string_value(uri_json);
235 ad->container = strdup(uri ? uri : "");
236 ad->auth = AUTH_DENY;
237 if (auth_json) {
238 const char *auth = json_string_value(auth_json);
239 if (!auth) {
240 auth = "";
241 }
242 if (!strcmp(auth, "allow")) {
243 ad->auth = AUTH_ALLOW;
244 } else if (!strcmp(auth, "deny")) {
245 ad->auth = AUTH_DENY;
246 } else {
247 PluginError("auth_directive has unknown auth parameter '%s', defaulting to deny: %s", auth, uri);
248 }
249 } else {
250 PluginError("auth_directive is missing auth parameter, defaulting to deny: %s", uri);
251 }
252 PluginDebug("Adding auth_directive %d for %s.", (int)ad->auth, ad->container);
253 }
254 }
255 ad->container = NULL;
256 }
257 } else {
258 PluginDebug("No auth_directives to load for %s.", *issuer);
259 }
260
261 json_t *key_ary = json_object_get(jwks, "keys");
262 if (!key_ary) {
263 PluginError("Failed to get keys member from jwk for issuer %s", *issuer);
264 *jwkis = NULL;
265 goto cfg_fail;
266 }
267 PluginDebug("Created table with size %d", cfg->issuers->size);
268
269 const char *renewal_kid = NULL;
270 json_t *renewal_kid_json = json_object_get(jwks, "renewal_kid");
271 if (renewal_kid_json) {
272 renewal_kid = json_string_value(renewal_kid_json);
273 }
274
275 json_t *id_json = json_object_get(jwks, "id");
276 const char *id;
277 if (id_json) {
278 id = json_string_value(id_json);
279 if (id) {
280 cfg->id = malloc(strlen(id) + 1);
281 strcpy(cfg->id, id);
282 PluginDebug("Found Id in the config: %s", cfg->id);
283 }
284 }
285
286 json_t *strip_json = json_object_get(jwks, "strip_token");
287 if (strip_json) {
288 cfg->strip_token = json_boolean_value(strip_json);
289 }
290
291 size_t jwks_ct = json_array_size(key_ary);
292 cjose_jwk_t **jwks = (*jwkis++ = malloc((jwks_ct + 1) * sizeof *jwks));
293 PluginDebug("Created table with size %d", cfg->issuers->size);
294 if (!hsearch_r(((ENTRY){*issuer, jwks}), ENTER, &(ENTRY *){0}, cfg->issuers)) {
295 PluginDebug("Failed to store keys for issuer %s", *issuer);
296 } else {
297 PluginDebug("Stored keys for %s at %16p", *issuer, jwks);
298 }
299
300 json_t *jwk_obj;
301 cjose_err jwk_err;
302 memset(&jwk_err, 0, sizeof(cjose_err));
303 for (size_t idx = 0; (idx < jwks_ct) && (jwk_obj = json_array_get(key_ary, idx)); ++idx, ++jwks) {
304 if ((*jwks = load_jwk(jwk_obj, &jwk_err))) {
305 const char *kid = cjose_jwk_get_kid(*jwks, NULL);
306 PluginDebug("Stored jwk %ld for issuer %s, kid %s, cfg %p->%p", idx, *issuer, kid ? kid : "<no kid>", cfg, cfg->issuers);
307 if (renewal_kid && kid && !strcmp(kid, renewal_kid)) {
308 if (cfg->signer.issuer) {
309 PluginError("Cannot load multiple renewal keys for a single remap. iss:\"%s\", kid:\"%s\"; iss:\"%s\", kid:\"%s\"",
310 cfg->signer.issuer, cjose_jwk_get_kid(cfg->signer.jwk, NULL), *issuer, kid);
311 goto cfg_fail;
312 } else {
313 cfg->signer.issuer = *issuer;
314 cfg->signer.jwk = *jwks;
315
316 const char *jwk_alg = json_string_value(json_object_get(jwk_obj, "alg"));
317 if (!jwk_alg) {
318 PluginError("Cannot load JWK algorithm for renewal key.");
319 goto cfg_fail;
320 }
321 cfg->signer.alg = strdup(jwk_alg);
322 }
323 }
324 } else {
325 PluginError("Failed to load jwk %ld for issuer %s: %s", idx, *issuer, jwk_err.message);
326 goto cfg_fail;
327 }
328 }
329 *jwks = NULL;
330 ++issuer;
331 }
332 if (!cfg->signer.issuer) {
333 PluginError("Cannot load remap without signing key.");
334 goto cfg_fail;
335 }
336 json_decref(issuer_json);
337 PluginDebug("Loaded config file successfully.");
338 return cfg;
339 cfg_fail:
340 config_delete(cfg);
341 issuer_fail:
342 json_decref(issuer_json);
343 return NULL;
344 }
345
346 struct config *
read_config_from_path(char const * const path)347 read_config_from_path(char const *const path)
348 {
349 json_error_t err;
350 memset(&err, 0, sizeof(json_error_t));
351 json_t *issuer_json = json_load_file(path, 0, &err);
352 if (!issuer_json) {
353 JSONError(err);
354 return NULL;
355 }
356 return read_config_from_json(issuer_json);
357 }
358
359 struct config *
read_config_from_string(char const * const buffer)360 read_config_from_string(char const *const buffer)
361 {
362 json_error_t err;
363 memset(&err, 0, sizeof(json_error_t));
364 json_t *issuer_json = json_loads(buffer, 0, &err);
365 if (!issuer_json) {
366 JSONError(err);
367 return NULL;
368 }
369 return read_config_from_json(issuer_json);
370 }
371
372 struct signer *
config_signer(struct config * cfg)373 config_signer(struct config *cfg)
374 {
375 if (!cfg) {
376 return NULL;
377 }
378 return &cfg->signer;
379 }
380
381 bool
uri_matches_auth_directive(struct config * cfg,const char * uri,size_t uri_ct)382 uri_matches_auth_directive(struct config *cfg, const char *uri, size_t uri_ct)
383 {
384 if (!cfg || !cfg->auth_directives || !uri) {
385 return false;
386 }
387
388 char *uri_s = malloc(uri_ct + 1);
389 memcpy(uri_s, uri, uri_ct);
390 uri_s[uri_ct] = 0;
391 for (const struct auth_directive *ad = cfg->auth_directives; ad->container; ++ad) {
392 if (jwt_check_uri(ad->container, uri_s)) {
393 free(uri_s);
394 return (ad->auth == AUTH_ALLOW);
395 }
396 }
397 free(uri_s);
398 return false;
399 }
400