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