/src/mod_auth_openidc/src/jose/jws.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 | | * Copyright (C) 2013-2017 Ping Identity Corporation |
23 | | * All rights reserved. |
24 | | * |
25 | | * DISCLAIMER OF WARRANTIES: |
26 | | * |
27 | | * THE SOFTWARE PROVIDED HEREUNDER IS PROVIDED ON AN "AS IS" BASIS, WITHOUT |
28 | | * ANY WARRANTIES OR REPRESENTATIONS EXPRESS, IMPLIED OR STATUTORY; INCLUDING, |
29 | | * WITHOUT LIMITATION, WARRANTIES OF QUALITY, PERFORMANCE, NONINFRINGEMENT, |
30 | | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. NOR ARE THERE ANY |
31 | | * WARRANTIES CREATED BY A COURSE OR DEALING, COURSE OF PERFORMANCE OR TRADE |
32 | | * USAGE. FURTHERMORE, THERE ARE NO WARRANTIES THAT THE SOFTWARE WILL MEET |
33 | | * YOUR NEEDS OR BE FREE FROM ERRORS, OR THAT THE OPERATION OF THE SOFTWARE |
34 | | * WILL BE UNINTERRUPTED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR |
35 | | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
36 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES HOWEVER CAUSED AND ON ANY THEORY OF |
37 | | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
38 | | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
39 | | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | | * |
41 | | * JSON Web Signature (JWS) / JWT signing, parsing and verification |
42 | | * |
43 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
44 | | */ |
45 | | |
46 | | #include "jose.h" |
47 | | |
48 | | #include "jose/internal.h" |
49 | | |
50 | | #include <jansson.h> |
51 | | |
52 | | #include <cjose/cjose.h> |
53 | | |
54 | | #include "util/util.h" |
55 | | |
56 | | /* |
57 | | * set a header value in a JWT |
58 | | */ |
59 | 20.3k | void oidc_jwt_hdr_set(oidc_jwt_t *jwt, const char *key, const char *value) { |
60 | 20.3k | json_object_set_new(jwt->header.value.json, key, json_string(value)); |
61 | 20.3k | } |
62 | | |
63 | | /* |
64 | | * create a new JWT |
65 | | */ |
66 | 21.7k | oidc_jwt_t *oidc_jwt_new(apr_pool_t *pool, int create_header, int create_payload) { |
67 | 21.7k | oidc_jwt_t *jwt = apr_pcalloc(pool, sizeof(oidc_jwt_t)); |
68 | 21.7k | if (create_header) { |
69 | 12.4k | jwt->header.value.json = json_object(); |
70 | 12.4k | } |
71 | 21.7k | if (create_payload) { |
72 | 0 | jwt->payload.value.json = json_object(); |
73 | 0 | } |
74 | 21.7k | return jwt; |
75 | 21.7k | } |
76 | | |
77 | | /* |
78 | | * get a header value from a JWT |
79 | | */ |
80 | 570 | const char *oidc_jwt_hdr_get(oidc_jwt_t *jwt, const char *key) { |
81 | 570 | cjose_err cjose_err; |
82 | 570 | cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); |
83 | 570 | return hdr ? cjose_header_get(hdr, key, &cjose_err) : NULL; |
84 | 570 | } |
85 | | |
86 | | /* |
87 | | * set a JWT header member to a raw (pre-serialized) JSON value |
88 | | */ |
89 | 0 | apr_byte_t oidc_jwt_hdr_set_json(oidc_jwt_t *jwt, const char *key, const char *raw_json, oidc_jose_error_t *err) { |
90 | 0 | json_error_t json_error; |
91 | 0 | json_t *value = json_loads(raw_json, 0, &json_error); |
92 | 0 | if (value == NULL) { |
93 | 0 | oidc_jose_error(err, "json_loads failed: %s", json_error.text); |
94 | 0 | return FALSE; |
95 | 0 | } |
96 | 0 | json_object_set_new(jwt->header.value.json, key, value); |
97 | 0 | return TRUE; |
98 | 0 | } |
99 | | |
100 | | /* base64url-encoded {"alg":"none"} JOSE header */ |
101 | 99 | #define OIDC_JOSE_HDR_ALG_NONE "eyJhbGciOiJub25lIn0" |
102 | | |
103 | | /* |
104 | | * perform compact serialization on a JWT and return the resulting string |
105 | | */ |
106 | 4.83k | char *oidc_jose_jwt_serialize(apr_pool_t *pool, oidc_jwt_t *jwt, oidc_jose_error_t *err) { |
107 | 4.83k | cjose_err cjose_err; |
108 | 4.83k | char *result = NULL; |
109 | 4.83k | const char *s_payload = NULL; |
110 | 4.83k | char *out = NULL; |
111 | 4.83k | size_t out_len; |
112 | | |
113 | 4.83k | if (_oidc_strcmp(jwt->header.alg, CJOSE_HDR_ALG_NONE) == 0) { |
114 | | |
115 | 99 | s_payload = |
116 | 99 | oidc_json_encode(pool, jwt->payload.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT); |
117 | 99 | if (s_payload == NULL) { |
118 | 0 | oidc_jose_error(err, "oidc_util_encode_json failed"); |
119 | 0 | return NULL; |
120 | 0 | } |
121 | | |
122 | | // out is allocated by cjose and must be freed explicitly by cjose_get_dealloc()() |
123 | 99 | if (cjose_base64url_encode((const uint8_t *)s_payload, _oidc_strlen(s_payload), &out, &out_len, |
124 | 99 | &cjose_err) == FALSE) { |
125 | 0 | oidc_jose_error(err, "cjose_base64url_encode failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
126 | 0 | return NULL; |
127 | 0 | } |
128 | | |
129 | 99 | result = apr_pstrmemdup(pool, out, out_len); |
130 | 99 | cjose_get_dealloc()(out); |
131 | 99 | result = apr_psprintf(pool, "%s.%s.", OIDC_JOSE_HDR_ALG_NONE, result); |
132 | | |
133 | 4.73k | } else { |
134 | | |
135 | | // out: "the returned string pointer is owned by the JWS, the caller should not attempt to free it |
136 | | // directly" |
137 | 4.73k | if (cjose_jws_export(jwt->cjose_jws, (const char **)&out, &cjose_err) == FALSE) { |
138 | 0 | oidc_jose_error(err, "cjose_jws_export failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
139 | 0 | return NULL; |
140 | 0 | } |
141 | | |
142 | 4.73k | result = apr_pstrdup(pool, out); |
143 | 4.73k | } |
144 | | |
145 | 4.83k | return result; |
146 | 4.83k | } |
147 | | |
148 | | /* |
149 | | * return the key type for an algorithm |
150 | | */ |
151 | 16.8k | int oidc_alg2kty(const char *alg) { |
152 | 16.8k | if (_oidc_strcmp(alg, CJOSE_HDR_ALG_DIR) == 0) |
153 | 11.0k | return CJOSE_JWK_KTY_OCT; |
154 | 5.86k | if (_oidc_strncmp(alg, "RS", 2) == 0) |
155 | 382 | return CJOSE_JWK_KTY_RSA; |
156 | 5.48k | if (_oidc_strncmp(alg, "PS", 2) == 0) |
157 | 243 | return CJOSE_JWK_KTY_RSA; |
158 | 5.24k | if (_oidc_strncmp(alg, "HS", 2) == 0) |
159 | 4.21k | return CJOSE_JWK_KTY_OCT; |
160 | 1.03k | #if (OIDC_JOSE_EC_SUPPORT) |
161 | 1.03k | if (_oidc_strncmp(alg, "ES", 2) == 0) |
162 | 234 | return CJOSE_JWK_KTY_EC; |
163 | 796 | #endif |
164 | 796 | if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) || |
165 | 713 | (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0)) |
166 | 292 | return CJOSE_JWK_KTY_OCT; |
167 | 504 | if (_oidc_strcmp(alg, CJOSE_HDR_ALG_RSA_OAEP) == 0) |
168 | 0 | return CJOSE_JWK_KTY_RSA; |
169 | 504 | return -1; |
170 | 504 | } |
171 | | |
172 | | /* |
173 | | * return the key type of a JWT |
174 | | */ |
175 | 5.33k | int oidc_jwt_alg2kty(const oidc_jwt_t *jwt) { |
176 | 5.33k | return oidc_alg2kty(jwt->header.alg); |
177 | 5.33k | } |
178 | | |
179 | | /* |
180 | | * return the key size for an algorithm |
181 | | */ |
182 | 4.71k | unsigned int oidc_alg2keysize(const char *alg) { |
183 | | |
184 | 4.71k | if (alg == NULL) |
185 | 2.32k | return 0; |
186 | | |
187 | 2.39k | if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A128KW) == 0) |
188 | 15 | return 16; |
189 | 2.37k | if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A192KW) == 0) |
190 | 2 | return 24; |
191 | 2.37k | if (_oidc_strcmp(alg, CJOSE_HDR_ALG_A256KW) == 0) |
192 | 234 | return 32; |
193 | | |
194 | 2.13k | if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS256) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS256) == 0) || |
195 | 2.13k | (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS256) == 0)) |
196 | 1.20k | return 32; |
197 | 930 | if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS384) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS384) == 0) || |
198 | 926 | (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS384) == 0)) |
199 | 7 | return 48; |
200 | 923 | if ((_oidc_strcmp(alg, CJOSE_HDR_ALG_RS512) == 0) || (_oidc_strcmp(alg, CJOSE_HDR_ALG_PS512) == 0) || |
201 | 919 | (_oidc_strcmp(alg, CJOSE_HDR_ALG_HS512) == 0)) |
202 | 7 | return 64; |
203 | | |
204 | 916 | return 0; |
205 | 923 | } |
206 | | |
207 | 4.49k | #define OIDC_JOSE_JWT_ISS "iss" |
208 | 4.49k | #define OIDC_JOSE_JWT_SUB "sub" |
209 | 4.49k | #define OIDC_JOSE_JWT_EXP "exp" |
210 | 4.49k | #define OIDC_JOSE_JWT_IAT "iat" |
211 | | |
212 | | /* |
213 | | * parse JWT payload |
214 | | */ |
215 | | static apr_byte_t oidc_jose_parse_payload(apr_pool_t *pool, const char *s_payload, size_t s_payload_len, |
216 | 4.58k | oidc_jwt_payload_t *payload, oidc_jose_error_t *err) { |
217 | | |
218 | | /* decode the string in to a JSON structure into value->json */ |
219 | 4.58k | json_error_t json_error; |
220 | 4.58k | payload->value.str = apr_pstrndup(pool, s_payload, s_payload_len); |
221 | 4.58k | payload->value.json = json_loads(payload->value.str, 0, &json_error); |
222 | | |
223 | | /* check that we've actually got a JSON value back */ |
224 | 4.58k | if (payload->value.json == NULL) { |
225 | 81 | oidc_jose_error(err, "JSON parsing (json_loads) failed: %s (%s)", json_error.text, payload->value.str); |
226 | 81 | return FALSE; |
227 | 81 | } |
228 | | |
229 | | /* check that the value is a JSON object */ |
230 | 4.50k | if (!json_is_object(payload->value.json)) { |
231 | 3 | oidc_jose_error(err, "JSON value is not an object"); |
232 | 3 | return FALSE; |
233 | 3 | } |
234 | | |
235 | | /* get the (optional) "iss" value from the JSON payload */ |
236 | 4.49k | oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_ISS, FALSE, &payload->iss, NULL); |
237 | | |
238 | | /* get the (optional) "exp" value from the JSON payload */ |
239 | 4.49k | oidc_jose_get_timestamp(payload->value.json, OIDC_JOSE_JWT_EXP, FALSE, &payload->exp, NULL); |
240 | | |
241 | | /* get the (optional) "iat" value from the JSON payload */ |
242 | 4.49k | oidc_jose_get_timestamp(payload->value.json, OIDC_JOSE_JWT_IAT, FALSE, &payload->iat, NULL); |
243 | | |
244 | | /* get the (optional) "sub" value from the JSON payload */ |
245 | 4.49k | oidc_jose_get_string(pool, payload->value.json, OIDC_JOSE_JWT_SUB, FALSE, &payload->sub, NULL); |
246 | | |
247 | 4.49k | return TRUE; |
248 | 4.50k | } |
249 | | |
250 | | /* |
251 | | * parse and (optionally) decrypt a JSON Web Token |
252 | | */ |
253 | | apr_byte_t oidc_jwt_parse(apr_pool_t *pool, const char *input_json, oidc_jwt_t **j_jwt, apr_hash_t *keys, |
254 | 9.59k | apr_byte_t compress, oidc_jose_error_t *err) { |
255 | | |
256 | 9.59k | cjose_err cjose_err; |
257 | 9.59k | char *s_json = NULL; |
258 | | |
259 | 9.59k | if (oidc_jwe_decrypt(pool, input_json, keys, &s_json, NULL, err, FALSE) == FALSE) |
260 | 315 | return FALSE; |
261 | | |
262 | 9.28k | *j_jwt = oidc_jwt_new(pool, FALSE, FALSE); |
263 | 9.28k | if (*j_jwt == NULL) |
264 | 0 | return FALSE; |
265 | 9.28k | oidc_jwt_t *jwt = *j_jwt; |
266 | | |
267 | 9.28k | jwt->cjose_jws = cjose_jws_import(s_json, _oidc_strlen(s_json), &cjose_err); |
268 | 9.28k | if (jwt->cjose_jws == NULL) { |
269 | 4.70k | oidc_jose_error(err, "cjose_jws_import failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
270 | 4.70k | oidc_jwt_destroy(jwt); |
271 | 4.70k | *j_jwt = NULL; |
272 | 4.70k | return FALSE; |
273 | 4.70k | } |
274 | | |
275 | 4.58k | cjose_header_t *hdr = cjose_jws_get_protected(jwt->cjose_jws); |
276 | 4.58k | jwt->header.value.json = json_copy((json_t *)hdr); |
277 | 4.58k | jwt->header.value.str = |
278 | 4.58k | oidc_json_encode(pool, jwt->header.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT); |
279 | | |
280 | 4.58k | jwt->header.alg = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ALG, &cjose_err)); |
281 | 4.58k | jwt->header.enc = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_ENC, &cjose_err)); |
282 | 4.58k | jwt->header.kid = apr_pstrdup(pool, cjose_header_get(hdr, CJOSE_HDR_KID, &cjose_err)); |
283 | | |
284 | 4.58k | uint8_t *plaintext = NULL; |
285 | 4.58k | size_t plaintext_len = 0; |
286 | 4.58k | if (cjose_jws_get_plaintext(jwt->cjose_jws, &plaintext, &plaintext_len, &cjose_err) == FALSE) { |
287 | 0 | oidc_jwt_destroy(jwt); |
288 | 0 | *j_jwt = NULL; |
289 | 0 | oidc_jose_error(err, "cjose_jws_get_plaintext failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
290 | 0 | return FALSE; |
291 | 0 | } |
292 | | |
293 | 4.58k | if (compress == TRUE) { |
294 | 0 | char *payload = NULL; |
295 | 0 | int payload_len = 0; |
296 | 0 | if (oidc_jose_uncompress(pool, (char *)plaintext, (int)plaintext_len, &payload, &payload_len, err) == |
297 | 0 | FALSE) { |
298 | 0 | oidc_jwt_destroy(jwt); |
299 | 0 | *j_jwt = NULL; |
300 | 0 | return FALSE; |
301 | 0 | } |
302 | 0 | plaintext = (uint8_t *)payload; |
303 | 0 | plaintext_len = payload_len; |
304 | 0 | } |
305 | | |
306 | 4.58k | if (oidc_jose_parse_payload(pool, (const char *)plaintext, plaintext_len, &jwt->payload, err) == FALSE) { |
307 | 84 | oidc_jwt_destroy(jwt); |
308 | 84 | *j_jwt = NULL; |
309 | 84 | return FALSE; |
310 | 84 | } |
311 | | |
312 | 4.49k | return TRUE; |
313 | 4.58k | } |
314 | | |
315 | | /* |
316 | | * destroy resources allocated for JWT |
317 | | */ |
318 | 22.3k | void oidc_jwt_destroy(oidc_jwt_t *jwt) { |
319 | 22.3k | if (jwt) { |
320 | 21.7k | if (jwt->header.value.json) { |
321 | 17.0k | json_decref(jwt->header.value.json); |
322 | 17.0k | jwt->header.value.json = NULL; |
323 | 17.0k | jwt->header.value.str = NULL; |
324 | 17.0k | } |
325 | 21.7k | if (jwt->payload.value.json) { |
326 | 9.04k | json_decref(jwt->payload.value.json); |
327 | 9.04k | jwt->payload.value.json = NULL; |
328 | 9.04k | jwt->payload.value.str = NULL; |
329 | 9.04k | } |
330 | 21.7k | if (jwt->cjose_jws) { |
331 | 9.12k | cjose_jws_release(jwt->cjose_jws); |
332 | 9.12k | jwt->cjose_jws = NULL; |
333 | 9.12k | } |
334 | 21.7k | } |
335 | 22.3k | } |
336 | | |
337 | | /* |
338 | | * sign a JWT |
339 | | */ |
340 | | apr_byte_t oidc_jwt_sign(apr_pool_t *pool, oidc_jwt_t *jwt, const oidc_jwk_t *jwk, apr_byte_t compress, |
341 | 4.54k | oidc_jose_error_t *err) { |
342 | | |
343 | 4.54k | cjose_header_t *hdr = (cjose_header_t *)jwt->header.value.json; |
344 | | |
345 | 4.54k | if (jwt->header.alg) |
346 | 4.54k | oidc_jwt_hdr_set(jwt, CJOSE_HDR_ALG, jwt->header.alg); |
347 | 4.54k | if (jwt->header.kid) |
348 | 0 | oidc_jwt_hdr_set(jwt, CJOSE_HDR_KID, jwt->header.kid); |
349 | 4.54k | if (jwt->header.enc) |
350 | 0 | oidc_jwt_hdr_set(jwt, CJOSE_HDR_ENC, jwt->header.enc); |
351 | 4.54k | if (jwt->header.x5t) |
352 | 0 | oidc_jwt_hdr_set(jwt, OIDC_JOSE_JWK_X5T_STR, jwt->header.x5t); |
353 | | |
354 | 4.54k | if (jwt->cjose_jws) |
355 | 0 | cjose_jws_release(jwt->cjose_jws); |
356 | | |
357 | 4.54k | cjose_err cjose_err; |
358 | 4.54k | char *plaintext = oidc_json_encode(pool, jwt->payload.value.json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT); |
359 | | |
360 | 4.54k | char *s_payload = NULL; |
361 | 4.54k | int payload_len = 0; |
362 | 4.54k | if (compress == TRUE) { |
363 | 0 | if (oidc_jose_compress(pool, plaintext, (int)_oidc_strlen(plaintext), &s_payload, &payload_len, err) == |
364 | 0 | FALSE) { |
365 | 0 | return FALSE; |
366 | 0 | } |
367 | 4.54k | } else { |
368 | 4.54k | s_payload = plaintext; |
369 | 4.54k | payload_len = (int)_oidc_strlen(plaintext); |
370 | 4.54k | jwt->payload.value.str = plaintext; |
371 | 4.54k | } |
372 | | |
373 | 4.54k | jwt->cjose_jws = cjose_jws_sign(jwk->cjose_jwk, hdr, (const uint8_t *)s_payload, payload_len, &cjose_err); |
374 | | |
375 | 4.54k | if (jwt->cjose_jws == NULL) { |
376 | 0 | oidc_jose_error(err, "cjose_jws_sign failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
377 | 0 | return FALSE; |
378 | 0 | } |
379 | | |
380 | 4.54k | return TRUE; |
381 | 4.54k | } |
382 | | |
383 | | /* |
384 | | * verify the signature of a JWT |
385 | | */ |
386 | | /* |
387 | | * verify a JWS against a single JWK, reporting failure into err and clearing |
388 | | * jwt->cjose_jws when the linked cjose version is known to leave it in an |
389 | | * unusable state after a failed verify |
390 | | */ |
391 | | static apr_byte_t oidc_jwt_verify_with_key(apr_pool_t *pool, oidc_jwt_t *jwt, const oidc_jwk_t *jwk, |
392 | 4.25k | oidc_jose_error_t *err) { |
393 | 4.25k | cjose_err cjose_err; |
394 | 4.25k | apr_byte_t rc = cjose_jws_verify(jwt->cjose_jws, jwk->cjose_jwk, &cjose_err); |
395 | 4.25k | if (rc == FALSE) { |
396 | 385 | oidc_jose_error(err, "cjose_jws_verify failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
397 | 385 | if (oidc_jose_version_deprecated(pool)) |
398 | 0 | jwt->cjose_jws = NULL; |
399 | 385 | } |
400 | 4.25k | return rc; |
401 | 4.25k | } |
402 | | |
403 | | /* |
404 | | * verify a JWS by iterating over all configured keys with a matching kty |
405 | | */ |
406 | 4.22k | static apr_byte_t oidc_jwt_verify_any(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) { |
407 | 4.22k | oidc_jwk_t *jwk = NULL; |
408 | 4.22k | apr_byte_t rc = FALSE; |
409 | | |
410 | 5.68k | for (apr_hash_index_t *hi = apr_hash_first(pool, keys); hi; hi = apr_hash_next(hi)) { |
411 | 5.32k | apr_hash_this(hi, NULL, NULL, (void **)&jwk); |
412 | 5.32k | if (jwk->kty == oidc_jwt_alg2kty(jwt)) |
413 | 4.24k | rc = oidc_jwt_verify_with_key(pool, jwt, jwk, err); |
414 | 5.32k | if ((rc == TRUE) || (jwt->cjose_jws == NULL)) |
415 | 3.86k | break; |
416 | 5.32k | } |
417 | | |
418 | 4.22k | if (rc == FALSE) |
419 | 362 | oidc_jose_error( |
420 | 4.22k | err, "could not verify signature against any of the (%d) provided keys%s", apr_hash_count(keys), |
421 | 4.22k | apr_hash_count(keys) > 0 |
422 | 4.22k | ? "" |
423 | 4.22k | : apr_psprintf(pool, |
424 | 4.22k | "; you have probably provided no or incorrect keys/key-types for algorithm: %s", |
425 | 4.22k | jwt->header.alg)); |
426 | | |
427 | 4.22k | return rc; |
428 | 4.22k | } |
429 | | |
430 | 4.23k | apr_byte_t oidc_jwt_verify(apr_pool_t *pool, oidc_jwt_t *jwt, apr_hash_t *keys, oidc_jose_error_t *err) { |
431 | | |
432 | 4.23k | if (jwt->header.kid == NULL) |
433 | 4.22k | return oidc_jwt_verify_any(pool, jwt, keys, err); |
434 | | |
435 | 11 | const oidc_jwk_t *jwk = apr_hash_get(keys, jwt->header.kid, APR_HASH_KEY_STRING); |
436 | 11 | if (jwk == NULL) { |
437 | 5 | oidc_jose_error(err, "could not find key with kid: %s", jwt->header.kid); |
438 | 5 | return FALSE; |
439 | 5 | } |
440 | | |
441 | | /* make sure the key type is compatible with the algorithm, just as the no-kid path does, rather than |
442 | | * relying solely on cjose to reject a mismatch (defense in depth against key/algorithm confusion) */ |
443 | 6 | if (jwk->kty != oidc_jwt_alg2kty(jwt)) { |
444 | 4 | oidc_jose_error(err, "key type of key with kid \"%s\" does not match the JWT \"alg\" header \"%s\"", |
445 | 4 | jwt->header.kid, jwt->header.alg); |
446 | 4 | return FALSE; |
447 | 4 | } |
448 | | |
449 | 2 | return oidc_jwt_verify_with_key(pool, jwt, jwk, err); |
450 | 6 | } |