/src/mod_auth_openidc/src/jose/jwk.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 Key (JWK) parsing, serialization and PEM/X.509 conversion |
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 <openssl/bn.h> |
55 | | #include <openssl/err.h> |
56 | | #include <openssl/evp.h> |
57 | | #include <openssl/opensslv.h> |
58 | | #include <openssl/pem.h> |
59 | | #include <openssl/rsa.h> |
60 | | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
61 | | #include <openssl/core_names.h> |
62 | | #endif |
63 | | |
64 | | #include "util/util.h" |
65 | | |
66 | | /* |
67 | | * extract a b64 encoded certificate representation as a single string |
68 | | */ |
69 | | static int oidc_jose_util_get_b64encoded_certificate_data(apr_pool_t *p, const X509 *x509_cert, |
70 | 19.0k | char **b64_encoded_certificate, oidc_jose_error_t *err) { |
71 | 19.0k | int rc = 0; |
72 | 19.0k | char *name = NULL; |
73 | 19.0k | char *header = NULL; |
74 | 19.0k | long len = 0; |
75 | 19.0k | long b64_len = 0; |
76 | 19.0k | BIO *bio = NULL; |
77 | 19.0k | unsigned char *data = NULL; |
78 | | |
79 | 19.0k | if ((bio = BIO_new(BIO_s_mem())) == NULL) { |
80 | 0 | oidc_jose_error_openssl(err, "BIO_new"); |
81 | 0 | goto end; |
82 | 0 | } |
83 | | |
84 | 19.0k | if (!PEM_write_bio_X509(bio, (X509 *)x509_cert)) { |
85 | 0 | oidc_jose_error_openssl(err, "PEM_write_bio_X509"); |
86 | 0 | goto end; |
87 | 0 | } |
88 | 19.0k | if (!PEM_read_bio(bio, &name, &header, &data, &len)) { |
89 | 0 | oidc_jose_error_openssl(err, "PEM_read_bio"); |
90 | 0 | goto end; |
91 | 0 | } |
92 | | |
93 | | /* "For every 3 bytes of input provided 4 bytes of output data will be produced." */ |
94 | 19.0k | b64_len = (((len + 2) / 3) * 4) + 1; |
95 | | |
96 | 19.0k | *b64_encoded_certificate = (char *)apr_pcalloc(p, b64_len); |
97 | 19.0k | if (!*b64_encoded_certificate) { |
98 | 0 | oidc_jose_error_openssl(err, "apr_pcalloc"); |
99 | 0 | goto end; |
100 | 0 | } |
101 | | |
102 | 19.0k | rc = EVP_EncodeBlock((unsigned char *)*b64_encoded_certificate, data, (int)len); |
103 | | |
104 | 19.0k | end: |
105 | 19.0k | if (bio) { |
106 | 19.0k | BIO_free(bio); |
107 | 19.0k | } |
108 | 19.0k | if (name != NULL) { |
109 | 19.0k | OPENSSL_free(name); |
110 | 19.0k | } |
111 | 19.0k | if (data != NULL) { |
112 | 19.0k | OPENSSL_free(data); |
113 | 19.0k | } |
114 | 19.0k | if (header != NULL) { |
115 | 19.0k | OPENSSL_free(header); |
116 | 19.0k | } |
117 | | |
118 | 19.0k | return rc; |
119 | 19.0k | } |
120 | | |
121 | | /* |
122 | | * create a new JWK |
123 | | */ |
124 | 108k | static oidc_jwk_t *oidc_jwk_new(apr_pool_t *pool) { |
125 | 108k | oidc_jwk_t *jwk = apr_pcalloc(pool, sizeof(oidc_jwk_t)); |
126 | 108k | return jwk; |
127 | 108k | } |
128 | | |
129 | | static apr_byte_t _oidc_jwk_parse_x5c(apr_pool_t *pool, const json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err); |
130 | | |
131 | 27.9k | #define OIDC_JOSE_HDR_KTY "kty" |
132 | 3.97k | #define OIDC_JOSE_HDR_KTY_RSA "RSA" |
133 | 3.24k | #define OIDC_JOSE_HDR_KTY_EC "EC" |
134 | 1.42k | #define OIDC_JOSE_HDR_X5C "x5c" |
135 | | |
136 | | /* |
137 | | * parse a JSON object with an "x5c" JWK representation into a cjose JWK object |
138 | | */ |
139 | 27.9k | static cjose_jwk_t *_oidc_jwk_parse_x5c_spec(apr_pool_t *pool, const json_t *json, oidc_jose_error_t *err) { |
140 | | |
141 | 27.9k | cjose_jwk_t *cjose_jwk = NULL; |
142 | | |
143 | 27.9k | char *kty = NULL; |
144 | 27.9k | oidc_jose_get_string(pool, json, OIDC_JOSE_HDR_KTY, FALSE, &kty, NULL); |
145 | 27.9k | if (kty == NULL) { |
146 | 23.9k | oidc_jose_error(err, "no key type \"" OIDC_JOSE_HDR_KTY "\" found in JWK JSON value"); |
147 | 23.9k | goto end; |
148 | 23.9k | } |
149 | | |
150 | 3.97k | if ((_oidc_strcmp(kty, OIDC_JOSE_HDR_KTY_RSA) != 0) && (_oidc_strcmp(kty, OIDC_JOSE_HDR_KTY_EC) != 0)) { |
151 | 3.06k | oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_KTY_RSA "\" or \"" OIDC_JOSE_HDR_KTY_EC |
152 | 3.06k | "\" key type found JWK JSON value"); |
153 | 3.06k | goto end; |
154 | 3.06k | } |
155 | | |
156 | 909 | const json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); |
157 | 909 | if (v == NULL) { |
158 | 397 | oidc_jose_error(err, "no \"" OIDC_JOSE_HDR_X5C "\" key found in JWK JSON value"); |
159 | 397 | goto end; |
160 | 397 | } |
161 | | |
162 | 512 | _oidc_jwk_parse_x5c(pool, json, &cjose_jwk, err); |
163 | | |
164 | 27.9k | end: |
165 | | |
166 | 27.9k | return cjose_jwk; |
167 | 512 | } |
168 | | |
169 | | /* |
170 | | * create a JWK struct from a cjose_jwk object |
171 | | */ |
172 | 11.9k | static oidc_jwk_t *oidc_jwk_from_cjose(apr_pool_t *pool, cjose_jwk_t *cjose_jwk, const char *use) { |
173 | 11.9k | cjose_err cjose_err; |
174 | 11.9k | oidc_jwk_t *jwk = oidc_jwk_new(pool); |
175 | 11.9k | jwk->cjose_jwk = cjose_jwk; |
176 | 11.9k | jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); |
177 | 11.9k | jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); |
178 | 11.9k | jwk->use = apr_pstrdup(pool, use); |
179 | 11.9k | return jwk; |
180 | 11.9k | } |
181 | | |
182 | | /* |
183 | | * parse a JSON string to a JWK struct |
184 | | */ |
185 | 54.9k | oidc_jwk_t *oidc_jwk_parse(apr_pool_t *pool, const json_t *json, oidc_jose_error_t *err) { |
186 | 54.9k | oidc_jwk_t *result = NULL; |
187 | 54.9k | cjose_jwk_t *cjose_jwk = NULL; |
188 | 54.9k | cjose_err cjose_err; |
189 | 54.9k | oidc_jose_error_t x5c_err; |
190 | 54.9k | char *use = NULL; |
191 | 54.9k | const json_t *v = NULL; |
192 | 54.9k | const json_t *e = NULL; |
193 | | |
194 | 54.9k | const char *s_json = oidc_json_encode(pool, json, OIDC_JSON_PRESERVE_ORDER | OIDC_JSON_COMPACT); |
195 | 54.9k | if (s_json == NULL) { |
196 | 15.1k | oidc_jose_error(err, "could not serialize JWK"); |
197 | 15.1k | goto end; |
198 | 15.1k | } |
199 | | |
200 | 39.8k | cjose_jwk = cjose_jwk_import(s_json, _oidc_strlen(s_json), &cjose_err); |
201 | | |
202 | 39.8k | if (cjose_jwk == NULL) { |
203 | | // exception because x5c is not supported by cjose natively |
204 | | // ignore errors set by oidc_jwk_parse_x5c_spec |
205 | 27.9k | cjose_jwk = _oidc_jwk_parse_x5c_spec(pool, json, &x5c_err); |
206 | 27.9k | if (cjose_jwk == NULL) { |
207 | 27.9k | oidc_jose_error(err, "JWK parsing failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
208 | 27.9k | goto end; |
209 | 27.9k | } |
210 | 27.9k | } |
211 | | |
212 | 11.9k | oidc_jose_get_string(pool, json, OIDC_JOSE_JWK_USE_STR, FALSE, &use, NULL); |
213 | | |
214 | 11.9k | result = oidc_jwk_from_cjose(pool, cjose_jwk, use); |
215 | | |
216 | | // set alg |
217 | 11.9k | oidc_jose_get_string(pool, json, OIDC_JOSE_JWK_ALG_STR, FALSE, &result->alg, NULL); |
218 | | |
219 | | // set x5c array |
220 | 11.9k | v = json_object_get(json, OIDC_JOSE_JWK_X5C_STR); |
221 | 11.9k | if (v && json_is_array(v)) { |
222 | 485 | result->x5c = apr_array_make(pool, (int)json_array_size(v), sizeof(const char *)); |
223 | 36.6k | for (size_t i = 0; i < json_array_size(v); i++) { |
224 | 36.1k | e = json_array_get(v, i); |
225 | 36.1k | if (json_is_string(e)) |
226 | 26.2k | APR_ARRAY_PUSH(result->x5c, const char *) = apr_pstrdup(pool, json_string_value(e)); |
227 | 36.1k | } |
228 | 485 | } |
229 | | |
230 | | // set x5t#256 |
231 | 11.9k | v = json_object_get(json, OIDC_JOSE_JWK_X5T256_STR); |
232 | 11.9k | if (v) |
233 | 336 | result->x5t_S256 = apr_pstrdup(pool, json_string_value(v)); |
234 | | |
235 | | // set x5t |
236 | 11.9k | v = json_object_get(json, OIDC_JOSE_JWK_X5T_STR); |
237 | 11.9k | if (v) |
238 | 207 | result->x5t = apr_pstrdup(pool, json_string_value(v)); |
239 | | |
240 | 54.9k | end: |
241 | | |
242 | 54.9k | return result; |
243 | 11.9k | } |
244 | | |
245 | | /* |
246 | | * copy a JWK by converting oidc_jwk_t to JSON and parsing it back |
247 | | */ |
248 | 9.41k | oidc_jwk_t *oidc_jwk_copy(apr_pool_t *pool, const oidc_jwk_t *src) { |
249 | 9.41k | cjose_err err; |
250 | 9.41k | oidc_jwk_t *dst = oidc_jwk_new(pool); |
251 | 9.41k | dst->cjose_jwk = cjose_jwk_retain(src->cjose_jwk, &err); |
252 | 9.41k | dst->kid = apr_pstrdup(pool, src->kid); |
253 | 9.41k | dst->kty = src->kty; |
254 | 9.41k | dst->use = apr_pstrdup(pool, src->use); |
255 | 9.41k | dst->alg = apr_pstrdup(pool, src->alg); |
256 | 9.41k | dst->x5c = NULL; |
257 | 9.41k | if (src->x5c) { |
258 | 485 | dst->x5c = apr_array_make(pool, src->x5c->nelts, sizeof(const char *)); |
259 | 26.7k | for (int i = 0; i < src->x5c->nelts; i++) |
260 | 26.2k | APR_ARRAY_PUSH(dst->x5c, const char *) = APR_ARRAY_IDX(src->x5c, i, const char *); |
261 | 485 | } |
262 | 9.41k | dst->x5t = apr_pstrdup(pool, src->x5t); |
263 | 9.41k | dst->x5t_S256 = apr_pstrdup(pool, src->x5t_S256); |
264 | 9.41k | return dst; |
265 | 9.41k | } |
266 | | |
267 | | /* |
268 | | * destroy resources allocated for a JWK struct |
269 | | */ |
270 | 105k | void oidc_jwk_destroy(oidc_jwk_t *jwk) { |
271 | 105k | if (jwk && jwk->cjose_jwk) { |
272 | 105k | cjose_jwk_release(jwk->cjose_jwk); |
273 | 105k | jwk->cjose_jwk = NULL; |
274 | 105k | } |
275 | 105k | } |
276 | | |
277 | | /* |
278 | | * destroy a list of JWKs structs |
279 | | */ |
280 | 10.1k | void oidc_jwk_list_destroy_hash(apr_hash_t *keys) { |
281 | 10.1k | const void *key = NULL; |
282 | 10.1k | apr_ssize_t klen = 0; |
283 | 10.1k | if (keys == NULL) |
284 | 0 | return; |
285 | 10.1k | for (apr_hash_index_t *hi = apr_hash_first(NULL, keys); hi; hi = apr_hash_next(hi)) { |
286 | 12 | oidc_jwk_t *jwk = NULL; |
287 | 12 | apr_hash_this(hi, &key, &klen, (void **)&jwk); |
288 | 12 | oidc_jwk_destroy(jwk); |
289 | 12 | apr_hash_set(keys, key, klen, NULL); |
290 | 12 | } |
291 | 10.1k | } |
292 | | |
293 | | /* |
294 | | * copy a list (array) of JWKs |
295 | | */ |
296 | 4.69k | apr_array_header_t *oidc_jwk_list_copy(apr_pool_t *pool, apr_array_header_t *src) { |
297 | 4.69k | apr_array_header_t *dst = NULL; |
298 | | |
299 | 4.69k | if (src == NULL) |
300 | 4.69k | return NULL; |
301 | | |
302 | 0 | dst = apr_array_make(pool, src->nelts, sizeof(const oidc_jwk_t *)); |
303 | 0 | for (int i = 0; i < src->nelts; i++) |
304 | 0 | APR_ARRAY_PUSH(dst, oidc_jwk_t *) = oidc_jwk_copy(pool, APR_ARRAY_IDX(src, i, const oidc_jwk_t *)); |
305 | |
|
306 | 0 | return dst; |
307 | 4.69k | } |
308 | | |
309 | | /* |
310 | | * destroy a list (array) of JWKs |
311 | | */ |
312 | 1.52k | void oidc_jwk_list_destroy(apr_array_header_t *keys_list) { |
313 | 1.52k | if (keys_list == NULL) |
314 | 0 | return; |
315 | 1.52k | oidc_jwk_t **jwk = NULL; |
316 | 13.1k | while ((jwk = apr_array_pop(keys_list))) { |
317 | 11.6k | oidc_jwk_destroy(*jwk); |
318 | 11.6k | } |
319 | 1.52k | } |
320 | | |
321 | | /* |
322 | | * parse a JSON object in to a JWK struct |
323 | | */ |
324 | 54.9k | apr_byte_t oidc_jwk_parse_json(apr_pool_t *pool, const json_t *json, oidc_jwk_t **jwk, oidc_jose_error_t *err) { |
325 | 54.9k | *jwk = oidc_jwk_parse(pool, json, err); |
326 | 54.9k | return (*jwk != NULL); |
327 | 54.9k | } |
328 | | |
329 | | /* |
330 | | * parse a set of JWKs into a list (array) of JWK structs |
331 | | */ |
332 | | apr_byte_t oidc_jwks_parse_json(apr_pool_t *pool, const json_t *json, apr_array_header_t **jwk_list, |
333 | 1.52k | oidc_jose_error_t *err) { |
334 | 1.52k | const json_t *keys = json_object_get(json, OIDC_JOSE_JWKS_KEYS_STR); |
335 | 1.52k | if ((keys == NULL) || (!json_is_array(keys))) { |
336 | 0 | oidc_jose_error(err, "JWKS did not contain \"" OIDC_JOSE_JWKS_KEYS_STR "\" array"); |
337 | 0 | return FALSE; |
338 | 0 | } |
339 | 1.52k | *jwk_list = apr_array_make(pool, (int)json_array_size(keys), sizeof(const oidc_jwk_t *)); |
340 | 13.1k | for (size_t i = 0; i < json_array_size(keys); i++) { |
341 | 12.7k | const json_t *elem = json_array_get(keys, i); |
342 | 12.7k | if (elem == NULL) |
343 | 0 | continue; |
344 | 12.7k | oidc_jwk_t *jwk; |
345 | 12.7k | if (oidc_jwk_parse_json(pool, elem, &jwk, err) != TRUE) { |
346 | | /* the keys parsed so far wrap non-pooled cjose/OpenSSL objects: release them rather |
347 | | * than hand the caller a half-built list it has no reason to clean up after a failure */ |
348 | 1.15k | oidc_jwk_list_destroy(*jwk_list); |
349 | 1.15k | *jwk_list = NULL; |
350 | 1.15k | return FALSE; |
351 | 1.15k | } |
352 | 11.6k | APR_ARRAY_PUSH(*jwk_list, const oidc_jwk_t *) = jwk; |
353 | 11.6k | } |
354 | 373 | return TRUE; |
355 | 1.52k | } |
356 | | |
357 | | /* |
358 | | * check if a JSON object is a JWK |
359 | | */ |
360 | 3.73k | apr_byte_t oidc_is_jwk(const json_t *json) { |
361 | 3.73k | const json_t *kty = json_object_get(json, OIDC_JOSE_JWK_KTY_STR); |
362 | 3.73k | if ((kty == NULL) || (!json_is_string(kty))) { |
363 | 45 | return FALSE; |
364 | 45 | } |
365 | 3.68k | return TRUE; |
366 | 3.73k | } |
367 | | |
368 | | /* |
369 | | * check if a JSON object is a set JWKs |
370 | | */ |
371 | 5.25k | apr_byte_t oidc_is_jwks(const json_t *json) { |
372 | 5.25k | const json_t *keys = json_object_get(json, OIDC_JOSE_JWKS_KEYS_STR); |
373 | 5.25k | if ((keys == NULL) || (!json_is_array(keys))) { |
374 | 3.73k | return FALSE; |
375 | 3.73k | } |
376 | 1.52k | return TRUE; |
377 | 5.25k | } |
378 | | |
379 | | /* |
380 | | * produce the serialized JSON JWK representation from an oidc_jwk_t structure |
381 | | */ |
382 | 9.68k | apr_byte_t oidc_jwk_to_json(apr_pool_t *pool, const oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *oidc_err) { |
383 | 9.68k | apr_byte_t rv = FALSE; |
384 | 9.68k | char *s_cjose = NULL; |
385 | 9.68k | cjose_err err; |
386 | 9.68k | json_t *json = NULL; |
387 | 9.68k | json_t *temp = NULL; |
388 | 9.68k | json_error_t json_error; |
389 | | |
390 | | // input sanity checks |
391 | 9.68k | if ((jwk == NULL) || (s_json == NULL)) |
392 | 0 | goto end; |
393 | | |
394 | | // get the JWK string representation from cjose |
395 | 9.68k | s_cjose = cjose_jwk_to_json(jwk->cjose_jwk, TRUE, &err); |
396 | 9.68k | if (s_cjose == NULL) { |
397 | 0 | oidc_jose_error(oidc_err, "oidc_jwk_to_json: cjose_jwk_to_json failed: %s", oidc_cjose_e2s(pool, err)); |
398 | 0 | goto end; |
399 | 0 | } |
400 | | |
401 | 9.68k | json = json_loads(s_cjose, 0, &json_error); |
402 | 9.68k | if (json == NULL) { |
403 | 0 | oidc_jose_error(oidc_err, "oidc_jwk_to_json: json_loads failed"); |
404 | 0 | goto end; |
405 | 0 | } |
406 | | |
407 | 9.68k | if (jwk->use) |
408 | 1.75k | json_object_set_new(json, OIDC_JOSE_JWK_USE_STR, json_string(jwk->use)); |
409 | | |
410 | | // set alg (RFC 7517 section 4.4); lets an OP pick this key for the named algorithm |
411 | 9.68k | if (jwk->alg) |
412 | 385 | json_object_set_new(json, OIDC_JOSE_JWK_ALG_STR, json_string(jwk->alg)); |
413 | | |
414 | | // set x5c |
415 | 9.68k | if ((jwk->x5c != NULL) && (jwk->x5c->nelts > 0)) { |
416 | 412 | temp = json_array(); |
417 | 45.6k | for (int i = 0; i < jwk->x5c->nelts; i++) |
418 | 45.2k | json_array_append_new(temp, json_string(APR_ARRAY_IDX(jwk->x5c, i, const char *))); |
419 | 412 | json_object_set_new(json, OIDC_JOSE_JWK_X5C_STR, temp); |
420 | 412 | } |
421 | | |
422 | | // set x5t#256 |
423 | 9.68k | if (jwk->x5t_S256 != NULL) |
424 | 364 | json_object_set_new(json, OIDC_JOSE_JWK_X5T256_STR, json_string(jwk->x5t_S256)); |
425 | | |
426 | | // set x5t |
427 | 9.68k | if (jwk->x5t != NULL) |
428 | 359 | json_object_set_new(json, OIDC_JOSE_JWK_X5T_STR, json_string(jwk->x5t)); |
429 | | |
430 | | // generate the string ... |
431 | 9.68k | *s_json = oidc_json_encode(pool, json, OIDC_JSON_ENCODE_ANY | OIDC_JSON_COMPACT | OIDC_JSON_PRESERVE_ORDER); |
432 | | |
433 | 9.68k | rv = (*s_json != NULL); |
434 | | |
435 | 9.68k | end: |
436 | | |
437 | 9.68k | if (json) |
438 | 9.68k | json_decref(json); |
439 | 9.68k | if (s_cjose) |
440 | 9.68k | cjose_get_dealloc()(s_cjose); |
441 | | |
442 | 9.68k | return rv; |
443 | 9.68k | } |
444 | | |
445 | | /* |
446 | | * convert the public part of a JWK struct to a (pool-allocated) JSON string; unlike oidc_jwk_to_json this |
447 | | * excludes private key material, which is required when publishing a key (e.g. the DPoP confirmation header) |
448 | | */ |
449 | 9.68k | apr_byte_t oidc_jwk_to_public_json(apr_pool_t *pool, const oidc_jwk_t *jwk, char **s_json, oidc_jose_error_t *err) { |
450 | 9.68k | cjose_err cjose_err; |
451 | 9.68k | char *s_cjose = NULL; |
452 | | |
453 | 9.68k | if ((jwk == NULL) || (s_json == NULL)) |
454 | 0 | return FALSE; |
455 | | |
456 | 9.68k | s_cjose = cjose_jwk_to_json(jwk->cjose_jwk, FALSE /* public only */, &cjose_err); |
457 | 9.68k | if (s_cjose == NULL) { |
458 | 0 | oidc_jose_error(err, "cjose_jwk_to_json failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
459 | 0 | return FALSE; |
460 | 0 | } |
461 | 9.68k | *s_json = apr_pstrdup(pool, s_cjose); |
462 | 9.68k | cjose_get_dealloc()(s_cjose); |
463 | | |
464 | 9.68k | return TRUE; |
465 | 9.68k | } |
466 | | |
467 | | /* |
468 | | * derive the default JWS signing algorithm for a key (RSA -> RS256; EC -> ES256/384/512 per curve); |
469 | | * returns NULL when the key type/curve is unsupported |
470 | | */ |
471 | 9.68k | const char *oidc_jwk_default_jws_alg(const oidc_jwk_t *jwk) { |
472 | 9.68k | if (jwk == NULL) |
473 | 0 | return NULL; |
474 | 9.68k | if (jwk->kty == OIDC_JOSE_JWK_KTY_RSA) |
475 | 1.60k | return OIDC_JOSE_HDR_ALG_RS256; |
476 | 8.08k | if (jwk->kty == OIDC_JOSE_JWK_KTY_EC) { |
477 | 1.42k | if (cjose_jwk_EC_get_curve(jwk->cjose_jwk, NULL) == NID_X9_62_prime256v1) |
478 | 640 | return OIDC_JOSE_HDR_ALG_ES256; |
479 | 788 | if (cjose_jwk_EC_get_curve(jwk->cjose_jwk, NULL) == NID_secp384r1) |
480 | 313 | return OIDC_JOSE_HDR_ALG_ES384; |
481 | 475 | if (cjose_jwk_EC_get_curve(jwk->cjose_jwk, NULL) == NID_secp521r1) |
482 | 475 | return OIDC_JOSE_HDR_ALG_ES512; |
483 | 475 | } |
484 | 6.65k | return NULL; |
485 | 8.08k | } |
486 | | |
487 | | /* |
488 | | * set a specified key identifier or generate a key identifier and set it |
489 | | */ |
490 | | static apr_byte_t oidc_jwk_set_or_generate_kid(apr_pool_t *pool, cjose_jwk_t *cjose_jwk, const char *s_kid, |
491 | 41.4k | const char *key_params, int key_params_len, oidc_jose_error_t *err) { |
492 | | |
493 | 41.4k | char *jwk_kid = NULL; |
494 | | |
495 | 41.4k | if (s_kid != NULL) { |
496 | 280 | jwk_kid = apr_pstrdup(pool, s_kid); |
497 | 41.1k | } else { |
498 | | /* calculate a unique key identifier (kid) by fingerprinting the key params */ |
499 | 41.1k | if (oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, key_params, key_params_len, |
500 | 41.1k | &jwk_kid, err) == FALSE) { |
501 | 0 | return FALSE; |
502 | 0 | } |
503 | 41.1k | } |
504 | | |
505 | 41.4k | cjose_err cjose_err; |
506 | 41.4k | if (cjose_jwk_set_kid(cjose_jwk, jwk_kid, _oidc_strlen(jwk_kid), &cjose_err) == FALSE) { |
507 | 0 | oidc_jose_error(err, "cjose_jwk_set_kid failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
508 | 0 | return FALSE; |
509 | 0 | } |
510 | | |
511 | 41.4k | return TRUE; |
512 | 41.4k | } |
513 | | |
514 | | /* |
515 | | * create an "oct" symmetric JWK |
516 | | */ |
517 | | oidc_jwk_t *oidc_jwk_create_symmetric_key(apr_pool_t *pool, const char *skid, const unsigned char *key, |
518 | 83.5k | unsigned int key_len, apr_byte_t set_kid, oidc_jose_error_t *err) { |
519 | | |
520 | 83.5k | cjose_err cjose_err; |
521 | 83.5k | cjose_jwk_t *cjose_jwk = cjose_jwk_create_oct_spec(key, key_len, &cjose_err); |
522 | 83.5k | if (cjose_jwk == NULL) { |
523 | 0 | oidc_jose_error(err, "cjose_jwk_create_oct_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
524 | 0 | return NULL; |
525 | 0 | } |
526 | | |
527 | 83.5k | if ((set_kid == TRUE) && |
528 | 41.1k | (oidc_jwk_set_or_generate_kid(pool, cjose_jwk, skid, (const char *)key, key_len, err) == FALSE)) { |
529 | 0 | cjose_jwk_release(cjose_jwk); |
530 | 0 | return NULL; |
531 | 0 | } |
532 | | |
533 | 83.5k | oidc_jwk_t *jwk = oidc_jwk_new(pool); |
534 | 83.5k | jwk->cjose_jwk = cjose_jwk; |
535 | 83.5k | jwk->kid = apr_pstrdup(pool, cjose_jwk_get_kid(jwk->cjose_jwk, &cjose_err)); |
536 | 83.5k | jwk->kty = cjose_jwk_get_kty(jwk->cjose_jwk, &cjose_err); |
537 | 83.5k | return jwk; |
538 | 83.5k | } |
539 | | |
540 | | /* |
541 | | * read an x509 certificate and its public key from the provided input |
542 | | */ |
543 | | static apr_byte_t oidc_jwk_x509_read(apr_pool_t *pool, BIO *input, char **encoded_certificate, EVP_PKEY **pkey, |
544 | 21.0k | X509 **rx509, oidc_jose_error_t *err) { |
545 | 21.0k | apr_byte_t rv = FALSE; |
546 | 21.0k | X509 *x509 = NULL; |
547 | 21.0k | int encoded_cert_len = 0; |
548 | | |
549 | | /* read the X.509 struct - assume input is no public key */ |
550 | 21.0k | if ((x509 = PEM_read_bio_X509_AUX(input, NULL, NULL, NULL)) == NULL) { |
551 | 2.02k | oidc_jose_error_openssl(err, "PEM_read_bio_X509_AUX"); |
552 | 2.02k | goto end; |
553 | 2.02k | } |
554 | | |
555 | 19.0k | if (pkey) { |
556 | | /* get the public key struct from the X.509 struct */ |
557 | 224 | *pkey = X509_get_pubkey(x509); |
558 | 224 | if (*pkey == NULL) { |
559 | 62 | oidc_jose_error_openssl(err, "X509_get_pubkey"); |
560 | 62 | goto end; |
561 | 62 | } |
562 | 224 | } |
563 | | |
564 | | /* populate x5c certificate */ |
565 | 19.0k | encoded_cert_len = oidc_jose_util_get_b64encoded_certificate_data(pool, x509, encoded_certificate, err); |
566 | | |
567 | 19.0k | rv = (encoded_certificate != NULL) && (encoded_cert_len > 0); |
568 | | |
569 | 21.0k | end: |
570 | 21.0k | if (x509) { |
571 | 19.0k | if (rx509) |
572 | 224 | *rx509 = x509; |
573 | 18.8k | else |
574 | 18.8k | X509_free(x509); |
575 | 19.0k | } |
576 | 21.0k | return rv; |
577 | 19.0k | } |
578 | | |
579 | | /* |
580 | | * extract a JWK struct and a fingerprint from an OpenSSL RSA key |
581 | | */ |
582 | | static apr_byte_t _oidc_jwk_rsa_key_to_jwk(apr_pool_t *pool, const EVP_PKEY *pkey, oidc_jwk_t **oidc_jwk, char **fp, |
583 | 77 | int *fp_len, oidc_jose_error_t *err) { |
584 | 77 | apr_byte_t rv = FALSE; |
585 | 77 | cjose_err cjose_err; |
586 | 77 | BIGNUM *rsa_n = NULL; |
587 | 77 | BIGNUM *rsa_e = NULL; |
588 | 77 | BIGNUM *rsa_d = NULL; |
589 | 77 | cjose_jwk_rsa_keyspec key_spec; |
590 | | |
591 | 77 | _oidc_memset(&key_spec, 0, sizeof(cjose_jwk_rsa_keyspec)); |
592 | | |
593 | 77 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
594 | | /* the modulus and exponent are required; a NULL BIGNUM here would crash BN_num_bytes() below */ |
595 | 77 | if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &rsa_n) || |
596 | 77 | !EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &rsa_e)) { |
597 | 0 | oidc_jose_error_openssl(err, "EVP_PKEY_get_bn_param(OSSL_PKEY_PARAM_RSA_N/E)"); |
598 | 0 | goto end; |
599 | 0 | } |
600 | 77 | EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &rsa_d); |
601 | | #else |
602 | | /* get the RSA key from the public key struct */ |
603 | | RSA *rsa = (RSA *)EVP_PKEY_get1_RSA((EVP_PKEY *)pkey); |
604 | | if (rsa == NULL) { |
605 | | oidc_jose_error_openssl(err, "EVP_PKEY_get1_RSA"); |
606 | | goto end; |
607 | | } |
608 | | |
609 | | #if OPENSSL_VERSION_NUMBER >= 0x10100005L && !defined(LIBRESSL_VERSION_NUMBER) |
610 | | RSA_get0_key(rsa, (const BIGNUM **)&rsa_n, (const BIGNUM **)&rsa_e, (const BIGNUM **)&rsa_d); |
611 | | #else |
612 | | rsa_n = rsa->n; |
613 | | rsa_e = rsa->e; |
614 | | rsa_d = rsa->d; |
615 | | #endif |
616 | | |
617 | | RSA_free(rsa); |
618 | | #endif |
619 | | |
620 | | /* convert the modulus bignum in to a key/len */ |
621 | 77 | key_spec.nlen = BN_num_bytes(rsa_n); |
622 | 77 | key_spec.n = apr_pcalloc(pool, key_spec.nlen); |
623 | 77 | BN_bn2bin(rsa_n, key_spec.n); |
624 | | |
625 | | /* convert the exponent bignum in to a key/len */ |
626 | 77 | key_spec.elen = BN_num_bytes(rsa_e); |
627 | 77 | key_spec.e = apr_pcalloc(pool, key_spec.elen); |
628 | 77 | BN_bn2bin(rsa_e, key_spec.e); |
629 | | |
630 | | /* convert the private exponent bignum in to a key/len */ |
631 | 77 | if (rsa_d != NULL) { |
632 | 26 | key_spec.dlen = BN_num_bytes(rsa_d); |
633 | 26 | key_spec.d = apr_pcalloc(pool, key_spec.dlen); |
634 | 26 | BN_bn2bin(rsa_d, key_spec.d); |
635 | 26 | } |
636 | | |
637 | 77 | (*oidc_jwk)->cjose_jwk = cjose_jwk_create_RSA_spec(&key_spec, &cjose_err); |
638 | 77 | if ((*oidc_jwk)->cjose_jwk == NULL) { |
639 | 13 | oidc_jose_error(err, "cjose_jwk_create_RSA_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
640 | 13 | goto end; |
641 | 13 | } |
642 | | |
643 | 64 | *fp_len = (int)(key_spec.nlen + key_spec.elen); |
644 | 64 | *fp = apr_pcalloc(pool, *fp_len); |
645 | 64 | _oidc_memcpy(*fp, key_spec.n, key_spec.nlen); |
646 | 64 | _oidc_memcpy(*fp + key_spec.nlen, key_spec.e, key_spec.elen); |
647 | | |
648 | 64 | rv = TRUE; |
649 | | |
650 | 77 | end: |
651 | | |
652 | 77 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
653 | 77 | if (rsa_n) |
654 | 77 | BN_clear_free(rsa_n); |
655 | 77 | if (rsa_e) |
656 | 77 | BN_clear_free(rsa_e); |
657 | 77 | if (rsa_d) |
658 | 26 | BN_clear_free(rsa_d); |
659 | 77 | #endif |
660 | | |
661 | 77 | return rv; |
662 | 64 | } |
663 | | |
664 | | #if (OIDC_JOSE_EC_SUPPORT) |
665 | | |
666 | | /* |
667 | | * extract a JWK struct and a fingerprint from an OpenSSL Elliptic Curve key |
668 | | */ |
669 | | static apr_byte_t _oidc_jwk_ec_key_to_jwk(apr_pool_t *pool, const EVP_PKEY *pkey, oidc_jwk_t **oidc_jwk, char **fp, |
670 | 222 | int *fp_len, oidc_jose_error_t *err) { |
671 | 222 | apr_byte_t rv = FALSE; |
672 | 222 | cjose_err cjose_err; |
673 | 222 | cjose_jwk_ec_keyspec ec_keyspec; |
674 | 222 | int crv = 0; |
675 | 222 | BIGNUM *ec_x = NULL; |
676 | 222 | BIGNUM *ec_y = NULL; |
677 | 222 | BIGNUM *ec_d = NULL; |
678 | | |
679 | 222 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
680 | 222 | char curve_name[64]; |
681 | 222 | size_t curve_name_len = 0; |
682 | | |
683 | | /* a parseable EC key can lack a public point (no affine coordinates); see OSS-Fuzz issue 550951150 */ |
684 | 222 | if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &ec_x) || |
685 | 219 | !EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &ec_y)) { |
686 | 3 | oidc_jose_error_openssl(err, "EVP_PKEY_get_bn_param(OSSL_PKEY_PARAM_EC_PUB_X/Y)"); |
687 | 3 | goto end; |
688 | 3 | } |
689 | 219 | EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_PRIV_KEY, &ec_d); |
690 | 219 | if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, curve_name, sizeof(curve_name), |
691 | 219 | &curve_name_len)) { |
692 | 0 | oidc_jose_error_openssl(err, "EVP_PKEY_get_utf8_string_param(OSSL_PKEY_PARAM_GROUP_NAME)"); |
693 | 0 | goto end; |
694 | 0 | } |
695 | 219 | crv = OBJ_sn2nid(curve_name); |
696 | | #else |
697 | | EC_KEY *eckey = (EC_KEY *)EVP_PKEY_get1_EC_KEY((EVP_PKEY *)pkey); |
698 | | if (eckey == NULL) { |
699 | | oidc_jose_error_openssl(err, "EVP_PKEY_get1_EC_KEY"); |
700 | | goto end; |
701 | | } |
702 | | const EC_GROUP *ec_group = EC_KEY_get0_group(eckey); |
703 | | const EC_POINT *ecpoint = EC_KEY_get0_public_key(eckey); |
704 | | crv = EC_GROUP_get_curve_name(ec_group); |
705 | | ec_x = BN_new(); |
706 | | ec_y = BN_new(); |
707 | | if (EC_POINT_get_affine_coordinates_GFp(ec_group, ecpoint, ec_x, ec_y, NULL) == 0) { |
708 | | oidc_jose_error_openssl(err, "EC_POINT_get_affine_coordinates_GFp"); |
709 | | EC_KEY_free(eckey); |
710 | | goto end; |
711 | | } |
712 | | /* NB: ec_d borrows from eckey; releasing our get1 reference here is safe since pkey keeps it alive */ |
713 | | ec_d = (BIGNUM *)EC_KEY_get0_private_key(eckey); |
714 | | if (crv == 0) { |
715 | | oidc_jose_error_openssl(err, "EC_GROUP_get_curve_name"); |
716 | | EC_KEY_free(eckey); |
717 | | goto end; |
718 | | } |
719 | | EC_KEY_free(eckey); |
720 | | #endif |
721 | | |
722 | 219 | _oidc_memset(&ec_keyspec, 0, sizeof(cjose_jwk_ec_keyspec)); |
723 | | |
724 | 219 | ec_keyspec.crv = crv; |
725 | | |
726 | 219 | ec_keyspec.xlen = BN_num_bytes(ec_x); |
727 | 219 | ec_keyspec.x = apr_pcalloc(pool, ec_keyspec.xlen); |
728 | 219 | BN_bn2bin(ec_x, ec_keyspec.x); |
729 | | |
730 | 219 | ec_keyspec.ylen = BN_num_bytes(ec_y); |
731 | 219 | ec_keyspec.y = apr_pcalloc(pool, ec_keyspec.ylen); |
732 | 219 | BN_bn2bin(ec_y, ec_keyspec.y); |
733 | | |
734 | 219 | if (ec_d != NULL) { |
735 | 86 | ec_keyspec.dlen = BN_num_bytes(ec_d); |
736 | 86 | ec_keyspec.d = apr_pcalloc(pool, ec_keyspec.dlen); |
737 | 86 | BN_bn2bin(ec_d, ec_keyspec.d); |
738 | 86 | } |
739 | | |
740 | 219 | (*oidc_jwk)->cjose_jwk = cjose_jwk_create_EC_spec(&ec_keyspec, &cjose_err); |
741 | 219 | if ((*oidc_jwk)->cjose_jwk == NULL) { |
742 | 2 | oidc_jose_error(err, "cjose_jwk_create_EC_spec failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
743 | 2 | goto end; |
744 | 2 | } |
745 | | |
746 | 217 | apr_uint32_t b = htonl(crv); |
747 | 217 | *fp_len = (int)(sizeof(b) + ec_keyspec.xlen + ec_keyspec.ylen); |
748 | 217 | *fp = apr_pcalloc(pool, *fp_len); |
749 | 217 | _oidc_memcpy(*fp, &b, sizeof(b)); |
750 | 217 | _oidc_memcpy(*fp + sizeof(b), ec_keyspec.x, ec_keyspec.xlen); |
751 | 217 | _oidc_memcpy(*fp + sizeof(b) + ec_keyspec.xlen, ec_keyspec.y, ec_keyspec.ylen); |
752 | | |
753 | 217 | rv = TRUE; |
754 | | |
755 | 222 | end: |
756 | | |
757 | 222 | if (ec_x) |
758 | 219 | BN_clear_free(ec_x); |
759 | 222 | if (ec_y) |
760 | 219 | BN_clear_free(ec_y); |
761 | 222 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
762 | 222 | if (ec_d) |
763 | 86 | BN_clear_free(ec_d); |
764 | 222 | #endif |
765 | 222 | return rv; |
766 | 217 | } |
767 | | |
768 | | #endif |
769 | | |
770 | | /* |
771 | | * convert the PEM public key - possibly in a X.509 certificate - in the BIO pointed to |
772 | | * by "input" to a JSON Web Key object |
773 | | */ |
774 | | /* |
775 | | * populate x5c (first cert plus any trailing chain entries) and the x5t/x5t#S256 |
776 | | * thumbprints on jwk from a parsed X.509 certificate |
777 | | */ |
778 | | static apr_byte_t oidc_jwk_populate_cert_info(apr_pool_t *pool, BIO *input, oidc_jwk_t *jwk, const X509 *x509, |
779 | 162 | const char *first_pem, oidc_jose_error_t *err) { |
780 | 162 | unsigned char *x509_bytes = NULL; |
781 | 162 | int x509_cert_length = 0; |
782 | 162 | char *next_pem = NULL; |
783 | 162 | apr_byte_t rv = FALSE; |
784 | | |
785 | 162 | jwk->x5c = apr_array_make(pool, 1, sizeof(const char *)); |
786 | 162 | if (jwk->x5c == NULL) { |
787 | 0 | oidc_jose_error(err, "apr_array_make failed"); |
788 | 0 | return FALSE; |
789 | 0 | } |
790 | 162 | APR_ARRAY_PUSH(jwk->x5c, const char *) = first_pem; |
791 | | |
792 | | #if OPENSSL_VERSION_NUMBER < 0x000907000L |
793 | | // openssl below 0.9.7 does not allocate memory for you :o |
794 | | x509_cert_length = i2d_X509((X509 *)x509, NULL); |
795 | | if (x509_cert_length <= 0) { |
796 | | oidc_jose_error_openssl(err, "i2d_X509"); |
797 | | goto end; |
798 | | } |
799 | | x509_bytes = (unsigned char *)OPENSSL_malloc(pool, x509_cert_length + 1); |
800 | | const unsigned char *p = x509_bytes; |
801 | | x509_cert_length = i2d_X509((X509 *)x509, &p); |
802 | | #else |
803 | 162 | x509_cert_length = i2d_X509((X509 *)x509, &x509_bytes); |
804 | 162 | #endif |
805 | 162 | if (x509_cert_length < 0) { |
806 | 0 | oidc_jose_error_openssl(err, "i2d_X509"); |
807 | 0 | goto end; |
808 | 0 | } |
809 | | |
810 | 162 | oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA1, (const char *)x509_bytes, x509_cert_length, |
811 | 162 | &jwk->x5t, err); |
812 | 162 | oidc_jose_hash_and_base64url_encode(pool, OIDC_JOSE_ALG_SHA256, (const char *)x509_bytes, x509_cert_length, |
813 | 162 | &jwk->x5t_S256, err); |
814 | | |
815 | 19.0k | while (oidc_jwk_x509_read(pool, input, &next_pem, NULL, NULL, err) == TRUE) |
816 | 18.8k | APR_ARRAY_PUSH(jwk->x5c, const char *) = next_pem; |
817 | | |
818 | 162 | rv = TRUE; |
819 | | |
820 | 162 | end: |
821 | 162 | if (x509_bytes) |
822 | 162 | OPENSSL_free(x509_bytes); |
823 | 162 | return rv; |
824 | 162 | } |
825 | | |
826 | | /* Read a bare PEM key or fall back to X.509; callers free a returned out_x509. */ |
827 | | static apr_byte_t oidc_jwk_pem_bio_read_public(apr_pool_t *pool, BIO *input, oidc_jwk_t *jwk, EVP_PKEY **pkey, |
828 | 2.10k | X509 **out_x509, oidc_jose_error_t *err) { |
829 | | |
830 | 2.10k | *pkey = PEM_read_bio_PUBKEY(input, NULL, NULL, NULL); |
831 | 2.10k | if (*pkey != NULL) |
832 | 22 | return TRUE; |
833 | | |
834 | | /* not a public key - reset the buffer and try as a certificate */ |
835 | 2.10k | BIO_reset(input); |
836 | | |
837 | 2.08k | char *first_pem = NULL; |
838 | 2.08k | if (oidc_jwk_x509_read(pool, input, &first_pem, pkey, out_x509, err) == FALSE) |
839 | 1.92k | return FALSE; |
840 | | |
841 | 162 | return oidc_jwk_populate_cert_info(pool, input, jwk, *out_x509, first_pem, err); |
842 | 2.08k | } |
843 | | |
844 | | /* |
845 | | * return the OpenSSL key-type base id for the supplied EVP_PKEY, abstracting |
846 | | * over the different OpenSSL API versions |
847 | | */ |
848 | 299 | static int oidc_jwk_pkey_base_id(const EVP_PKEY *pkey) { |
849 | 299 | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
850 | 299 | return EVP_PKEY_get_base_id(pkey); |
851 | | #elif (OPENSSL_VERSION_NUMBER > 0x10100000) |
852 | | return EVP_PKEY_base_id(pkey); |
853 | | #else |
854 | | return EVP_PKEY_type(pkey->type); |
855 | | #endif |
856 | 299 | } |
857 | | |
858 | | /* |
859 | | * dispatch a parsed EVP_PKEY into the matching JWK builder, producing the |
860 | | * fingerprint bytes that drive kid generation |
861 | | */ |
862 | | static apr_byte_t oidc_jwk_pkey_to_jwk(apr_pool_t *pool, const EVP_PKEY *pkey, oidc_jwk_t **jwk, char **fp, int *fp_len, |
863 | 299 | oidc_jose_error_t *err) { |
864 | 299 | switch (oidc_jwk_pkey_base_id(pkey)) { |
865 | 77 | case EVP_PKEY_RSA: |
866 | 77 | return _oidc_jwk_rsa_key_to_jwk(pool, pkey, jwk, fp, fp_len, err); |
867 | 0 | #if (OIDC_JOSE_EC_SUPPORT) |
868 | 222 | case EVP_PKEY_EC: |
869 | 222 | return _oidc_jwk_ec_key_to_jwk(pool, pkey, jwk, fp, fp_len, err); |
870 | 0 | #endif |
871 | 0 | default: |
872 | 0 | oidc_jose_error(err, "unhandled key type: %d", oidc_jwk_pkey_base_id(pkey)); |
873 | 0 | return FALSE; |
874 | 299 | } |
875 | 299 | } |
876 | | |
877 | | apr_byte_t oidc_jwk_pem_bio_to_jwk(apr_pool_t *pool, BIO *input, const char *kid, oidc_jwk_t **oidc_jwk, |
878 | 3.70k | apr_byte_t is_private_key, oidc_jose_error_t *err) { |
879 | 3.70k | cjose_err cjose_err; |
880 | 3.70k | X509 *x509 = NULL; |
881 | 3.70k | EVP_PKEY *pkey = NULL; |
882 | 3.70k | apr_byte_t rv = FALSE; |
883 | 3.70k | char *fp = NULL; |
884 | 3.70k | int fp_len = 0; |
885 | | |
886 | 3.70k | *oidc_jwk = oidc_jwk_new(pool); |
887 | | |
888 | 3.70k | if (is_private_key == TRUE) { |
889 | 1.59k | if ((pkey = PEM_read_bio_PrivateKey(input, NULL, NULL, NULL)) == NULL) { |
890 | 1.48k | oidc_jose_error_openssl(err, "PEM_read_bio_PrivateKey"); |
891 | 1.48k | goto end; |
892 | 1.48k | } |
893 | 2.10k | } else if (oidc_jwk_pem_bio_read_public(pool, input, *oidc_jwk, &pkey, &x509, err) == FALSE) { |
894 | 1.92k | goto end; |
895 | 1.92k | } |
896 | | |
897 | 299 | if (oidc_jwk_pkey_to_jwk(pool, pkey, oidc_jwk, &fp, &fp_len, err) == FALSE) |
898 | 18 | goto end; |
899 | | |
900 | 281 | if (oidc_jwk_set_or_generate_kid(pool, (*oidc_jwk)->cjose_jwk, kid, fp, fp_len, err) == FALSE) |
901 | 0 | goto end; |
902 | | |
903 | 281 | (*oidc_jwk)->kid = apr_pstrdup(pool, cjose_jwk_get_kid((*oidc_jwk)->cjose_jwk, &cjose_err)); |
904 | 281 | (*oidc_jwk)->kty = cjose_jwk_get_kty((*oidc_jwk)->cjose_jwk, &cjose_err); |
905 | | |
906 | 281 | rv = TRUE; |
907 | | |
908 | 3.70k | end: |
909 | | |
910 | 3.70k | if (pkey) |
911 | 299 | EVP_PKEY_free(pkey); |
912 | 3.70k | if (x509) |
913 | 224 | X509_free(x509); |
914 | | |
915 | 3.70k | return rv; |
916 | 281 | } |
917 | | |
918 | | /* |
919 | | * parse a PEM-formatted public or private key from the specified file |
920 | | */ |
921 | | static apr_byte_t oidc_jwk_parse_pem_key(apr_pool_t *pool, apr_byte_t is_private_key, const char *kid, |
922 | 0 | const char *filename, oidc_jwk_t **jwk, oidc_jose_error_t *err) { |
923 | 0 | BIO *input = NULL; |
924 | 0 | apr_byte_t rv = FALSE; |
925 | |
|
926 | 0 | if ((input = BIO_new(BIO_s_file())) == NULL) { |
927 | 0 | oidc_jose_error_openssl(err, "BIO_new/BIO_s_file"); |
928 | 0 | goto end; |
929 | 0 | } |
930 | | |
931 | 0 | if (BIO_read_filename(input, filename) <= 0) { |
932 | 0 | oidc_jose_error_openssl(err, "BIO_read_filename"); |
933 | 0 | goto end; |
934 | 0 | } |
935 | | |
936 | 0 | if (oidc_jwk_pem_bio_to_jwk(pool, input, kid, jwk, is_private_key, err) == FALSE) |
937 | 0 | goto end; |
938 | | |
939 | 0 | rv = TRUE; |
940 | |
|
941 | 0 | end: |
942 | |
|
943 | 0 | if (input) |
944 | 0 | BIO_free(input); |
945 | |
|
946 | 0 | return rv; |
947 | 0 | } |
948 | | |
949 | 1.01k | #define OIDC_JOSE_CERT_BEGIN "-----BEGIN CERTIFICATE-----" |
950 | 1.01k | #define OIDC_JOSE_CERT_END "-----END CERTIFICATE-----" |
951 | | |
952 | | /* |
953 | | * parse a PEM-formatted key from a JSON object in to a cjose JWK object |
954 | | */ |
955 | 512 | static apr_byte_t _oidc_jwk_parse_x5c(apr_pool_t *pool, const json_t *json, cjose_jwk_t **jwk, oidc_jose_error_t *err) { |
956 | | |
957 | 512 | apr_byte_t rv = FALSE; |
958 | 512 | const char *kid = NULL; |
959 | 512 | oidc_jwk_t *oidc_jwk = NULL; |
960 | | |
961 | | /* get the "x5c" array element from the JSON object */ |
962 | 512 | const json_t *v = json_object_get(json, OIDC_JOSE_HDR_X5C); |
963 | 512 | if (v == NULL) { |
964 | 0 | oidc_jose_error(err, "JSON key \"%s\" could not be found", OIDC_JOSE_HDR_X5C); |
965 | 0 | return FALSE; |
966 | 0 | } |
967 | 512 | if (!json_is_array(v)) { |
968 | 1 | oidc_jose_error(err, "JSON key \"%s\" was found but its value is not a JSON array", OIDC_JOSE_HDR_X5C); |
969 | 1 | return FALSE; |
970 | 1 | } |
971 | | |
972 | | /* take the first element of the array */ |
973 | 511 | v = json_array_get(v, 0); |
974 | 511 | if (v == NULL) { |
975 | 1 | oidc_jose_error(err, "first element in JSON array is \"null\""); |
976 | 1 | return FALSE; |
977 | 1 | } |
978 | 510 | if (!json_is_string(v)) { |
979 | 4 | oidc_jose_error(err, "first element in array is not a JSON string"); |
980 | 4 | return FALSE; |
981 | 4 | } |
982 | | |
983 | 506 | const char *s_x5c = json_string_value(v); |
984 | | |
985 | | /* |
986 | | * PEM-format it: the header, the base64 data in lines of at most "chunk" characters, the footer. |
987 | | * The result is sized and allocated once up front: the x5c value comes from the provider's JWKS |
988 | | * (up to the HTTP response limit) and re-printing the accumulated string per line, as was done |
989 | | * before, made the pool memory consumed by this function quadratic in its length (OSS-Fuzz 551146117) |
990 | | */ |
991 | 506 | const size_t chunk = 75; |
992 | 506 | const size_t n = _oidc_strlen(s_x5c); |
993 | 506 | const size_t n_lines = (n + chunk - 1) / chunk; |
994 | 506 | const size_t len = _oidc_strlen(OIDC_JOSE_CERT_BEGIN) + 1 + n + n_lines + _oidc_strlen(OIDC_JOSE_CERT_END) + 1; |
995 | 506 | char *s = apr_palloc(pool, len + 1); |
996 | 506 | char *p = s; |
997 | | |
998 | 506 | _oidc_memcpy(p, OIDC_JOSE_CERT_BEGIN, _oidc_strlen(OIDC_JOSE_CERT_BEGIN)); |
999 | 506 | p += _oidc_strlen(OIDC_JOSE_CERT_BEGIN); |
1000 | 506 | *p++ = '\n'; |
1001 | 55.5k | for (size_t i = 0; i < n; i += chunk) { |
1002 | 55.0k | const size_t m = ((n - i) < chunk) ? (n - i) : chunk; |
1003 | 55.0k | _oidc_memcpy(p, s_x5c + i, m); |
1004 | 55.0k | p += m; |
1005 | 55.0k | *p++ = '\n'; |
1006 | 55.0k | } |
1007 | 506 | _oidc_memcpy(p, OIDC_JOSE_CERT_END, _oidc_strlen(OIDC_JOSE_CERT_END)); |
1008 | 506 | p += _oidc_strlen(OIDC_JOSE_CERT_END); |
1009 | 506 | *p++ = '\n'; |
1010 | 506 | *p = '\0'; |
1011 | | |
1012 | | /* put it in BIO memory */ |
1013 | 506 | BIO *input = BIO_new_mem_buf(s, (int)(p - s)); |
1014 | 506 | if (input == NULL) { |
1015 | 0 | oidc_jose_error_openssl(err, "BIO_new_mem_buf"); |
1016 | 0 | return FALSE; |
1017 | 0 | } |
1018 | | |
1019 | 506 | v = json_object_get(json, CJOSE_HDR_KID); |
1020 | 506 | if ((v != NULL) && json_is_string(v)) { |
1021 | 45 | kid = json_string_value(v); |
1022 | 45 | } |
1023 | | |
1024 | | /* do the actual parsing */ |
1025 | | |
1026 | 506 | rv = oidc_jwk_pem_bio_to_jwk(pool, input, kid, &oidc_jwk, FALSE, err); |
1027 | 506 | *jwk = oidc_jwk->cjose_jwk; |
1028 | | |
1029 | 506 | BIO_free(input); |
1030 | | |
1031 | 506 | return rv; |
1032 | 506 | } |
1033 | | |
1034 | | /* |
1035 | | * parse a PEM formatted private key to a JWK |
1036 | | */ |
1037 | | apr_byte_t oidc_jwk_parse_pem_private_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, |
1038 | 0 | oidc_jose_error_t *err) { |
1039 | 0 | return oidc_jwk_parse_pem_key(pool, TRUE, kid, filename, jwk, err); |
1040 | 0 | } |
1041 | | |
1042 | | /* |
1043 | | * parse a PEM formatted public key file to a JWK |
1044 | | */ |
1045 | | apr_byte_t oidc_jwk_parse_pem_public_key(apr_pool_t *pool, const char *kid, const char *filename, oidc_jwk_t **jwk, |
1046 | 0 | oidc_jose_error_t *err) { |
1047 | 0 | return oidc_jwk_parse_pem_key(pool, FALSE, kid, filename, jwk, err); |
1048 | 0 | } |