/src/mod_auth_openidc/src/jose.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 Token handling |
42 | | * |
43 | | * @Author: Hans Zandbelt - hans.zandbelt@openidc.com |
44 | | */ |
45 | | |
46 | | #include <apr_base64.h> |
47 | | #define APR_WANT_BYTEFUNC |
48 | | #include <apr_want.h> |
49 | | |
50 | | #ifdef USE_LIBBROTLI |
51 | | #include <brotli/decode.h> |
52 | | #include <brotli/encode.h> |
53 | | #endif |
54 | | #ifdef USE_ZLIB |
55 | | #include <zlib.h> |
56 | | #endif |
57 | | |
58 | | #include "jose.h" |
59 | | |
60 | | /* cjose exposes the backend JSON type, so this file is the only raw JSON user outside json.c. */ |
61 | | #include <jansson.h> |
62 | | |
63 | | #include <cjose/cjose.h> |
64 | | |
65 | | #include <openssl/bn.h> |
66 | | #include <openssl/err.h> |
67 | | #include <openssl/evp.h> |
68 | | #include <openssl/hmac.h> |
69 | | #include <openssl/opensslv.h> |
70 | | #include <openssl/pem.h> |
71 | | #include <openssl/rsa.h> |
72 | | #if OPENSSL_VERSION_NUMBER >= 0x30000000L |
73 | | #include <openssl/core_names.h> |
74 | | #endif |
75 | | |
76 | | #include "util/util.h" |
77 | | |
78 | | #include "jose/internal.h" |
79 | | |
80 | | /* |
81 | | * assemble an error report |
82 | | */ |
83 | | void _oidc_jose_error_set(oidc_jose_error_t *error, const char *source, const int line, const char *function, |
84 | 0 | const char *fmt, ...) { |
85 | 0 | if (error == NULL) |
86 | 0 | return; |
87 | 0 | snprintf(error->source, OIDC_JOSE_ERROR_SOURCE_LENGTH, "%s", source); |
88 | 0 | error->line = line; |
89 | 0 | snprintf(error->function, OIDC_JOSE_ERROR_FUNCTION_LENGTH, "%s", function); |
90 | 0 | va_list ap; |
91 | 0 | va_start(ap, fmt); |
92 | 0 | vsnprintf(error->text, OIDC_JOSE_ERROR_TEXT_LENGTH, fmt ? fmt : "(null)", ap); |
93 | 0 | va_end(ap); |
94 | 0 | } |
95 | | |
96 | | /* |
97 | | * hash a sequence of bytes with a specific algorithm and return the result as a base64url-encoded \0 terminated string |
98 | | */ |
99 | | apr_byte_t oidc_jose_hash_and_base64url_encode(apr_pool_t *pool, const char *openssl_hash_algo, const char *input, |
100 | 0 | int input_len, char **output, oidc_jose_error_t *err) { |
101 | 0 | unsigned char *hashed = NULL; |
102 | 0 | unsigned int hashed_len = 0; |
103 | 0 | if (oidc_jose_hash_bytes(pool, openssl_hash_algo, (const unsigned char *)input, input_len, &hashed, &hashed_len, |
104 | 0 | err) == FALSE) |
105 | 0 | return FALSE; |
106 | 0 | char *out = NULL; |
107 | 0 | size_t out_len; |
108 | 0 | cjose_err cjose_err; |
109 | 0 | if (cjose_base64url_encode(hashed, hashed_len, &out, &out_len, &cjose_err) == FALSE) { |
110 | 0 | oidc_jose_error(err, "cjose_base64url_encode failed: %s", oidc_cjose_e2s(pool, cjose_err)); |
111 | 0 | return FALSE; |
112 | 0 | } |
113 | 0 | *output = apr_pstrmemdup(pool, out, out_len); |
114 | 0 | cjose_get_dealloc()(out); |
115 | 0 | return TRUE; |
116 | 0 | } |
117 | | |
118 | | /* |
119 | | * check if a string is an element of an array of strings |
120 | | */ |
121 | 0 | static apr_byte_t oidc_jose_array_has_string(apr_array_header_t *haystack, const char *needle) { |
122 | 0 | int i = 0; |
123 | 0 | while (i < haystack->nelts) { |
124 | 0 | if (_oidc_strcmp(APR_ARRAY_IDX(haystack, i, const char *), needle) == 0) |
125 | 0 | return TRUE; |
126 | 0 | i++; |
127 | 0 | } |
128 | 0 | return FALSE; |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * return all supported signing algorithms |
133 | | */ |
134 | 0 | apr_array_header_t *oidc_jose_jws_supported_algorithms(apr_pool_t *pool) { |
135 | 0 | apr_array_header_t *result = apr_array_make(pool, 12, sizeof(const char *)); |
136 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS256; |
137 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS384; |
138 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RS512; |
139 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS256; |
140 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS384; |
141 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_PS512; |
142 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS256; |
143 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS384; |
144 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_HS512; |
145 | 0 | #if (OIDC_JOSE_EC_SUPPORT) |
146 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES256; |
147 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES384; |
148 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_ES512; |
149 | 0 | #endif |
150 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_NONE; |
151 | 0 | return result; |
152 | 0 | } |
153 | | |
154 | | /* |
155 | | * check if the provided signing algorithm is supported |
156 | | */ |
157 | 0 | apr_byte_t oidc_jose_jws_algorithm_is_supported(apr_pool_t *pool, const char *alg) { |
158 | 0 | return oidc_jose_array_has_string(oidc_jose_jws_supported_algorithms(pool), alg); |
159 | 0 | } |
160 | | |
161 | | /* |
162 | | * return all supported content encryption key algorithms |
163 | | */ |
164 | 0 | apr_array_header_t *oidc_jose_jwe_supported_algorithms(apr_pool_t *pool) { |
165 | 0 | apr_array_header_t *result = apr_array_make(pool, 4, sizeof(const char *)); |
166 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A128KW; |
167 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A192KW; |
168 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_A256KW; |
169 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ALG_RSA_OAEP; |
170 | 0 | return result; |
171 | 0 | } |
172 | | |
173 | | /* |
174 | | * check if the provided content encryption key algorithm is supported |
175 | | */ |
176 | 0 | apr_byte_t oidc_jose_jwe_algorithm_is_supported(apr_pool_t *pool, const char *alg) { |
177 | 0 | return oidc_jose_array_has_string(oidc_jose_jwe_supported_algorithms(pool), alg); |
178 | 0 | } |
179 | | |
180 | | /* |
181 | | * return all supported encryption algorithms |
182 | | */ |
183 | 0 | apr_array_header_t *oidc_jose_jwe_supported_encryptions(apr_pool_t *pool) { |
184 | 0 | apr_array_header_t *result = apr_array_make(pool, 5, sizeof(const char *)); |
185 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A128CBC_HS256; |
186 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A192CBC_HS384; |
187 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A256CBC_HS512; |
188 | 0 | #if (OIDC_JOSE_GCM_SUPPORT) |
189 | 0 | APR_ARRAY_PUSH(result, const char *) = CJOSE_HDR_ENC_A256GCM; |
190 | 0 | #endif |
191 | 0 | return result; |
192 | 0 | } |
193 | | |
194 | | /* |
195 | | * check if the provided encryption algorithm is supported |
196 | | */ |
197 | 0 | apr_byte_t oidc_jose_jwe_encryption_is_supported(apr_pool_t *pool, const char *enc) { |
198 | 0 | return oidc_jose_array_has_string(oidc_jose_jwe_supported_encryptions(pool), enc); |
199 | 0 | } |
200 | | |
201 | | /* |
202 | | * get (optional) string from JWT |
203 | | */ |
204 | | apr_byte_t oidc_jose_get_string(apr_pool_t *pool, const json_t *json, const char *claim_name, apr_byte_t is_mandatory, |
205 | 0 | char **result, oidc_jose_error_t *err) { |
206 | 0 | const json_t *v = json_object_get(json, claim_name); |
207 | 0 | if (v != NULL) { |
208 | 0 | if (json_is_string(v)) { |
209 | 0 | *result = apr_pstrdup(pool, json_string_value(v)); |
210 | 0 | } else if (is_mandatory) { |
211 | 0 | oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a string", |
212 | 0 | claim_name); |
213 | 0 | return FALSE; |
214 | 0 | } |
215 | 0 | } else if (is_mandatory) { |
216 | 0 | oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); |
217 | 0 | return FALSE; |
218 | 0 | } |
219 | 0 | return TRUE; |
220 | 0 | } |
221 | | |
222 | | /* |
223 | | * parse (optional) timestamp from payload |
224 | | */ |
225 | | apr_byte_t oidc_jose_get_timestamp(const json_t *json, const char *claim_name, apr_byte_t is_mandatory, double *result, |
226 | 0 | oidc_jose_error_t *err) { |
227 | 0 | *result = OIDC_JWT_CLAIM_TIME_EMPTY; |
228 | 0 | const json_t *v = json_object_get(json, claim_name); |
229 | 0 | if (v != NULL) { |
230 | 0 | if (json_is_number(v)) { |
231 | 0 | *result = json_number_value(v); |
232 | 0 | } else if (is_mandatory) { |
233 | 0 | oidc_jose_error(err, "mandatory JSON key \"%s\" was found but the type is not a number", |
234 | 0 | claim_name); |
235 | 0 | return FALSE; |
236 | 0 | } |
237 | 0 | } else if (is_mandatory) { |
238 | 0 | oidc_jose_error(err, "mandatory JSON key \"%s\" could not be found", claim_name); |
239 | 0 | return FALSE; |
240 | 0 | } |
241 | 0 | return TRUE; |
242 | 0 | } |
243 | | |
244 | 0 | #define OIDC_CJOSE_UNCOMPRESS_CHUNK 8192 |
245 | | /* absolute cap on the decompressed output to prevent decompression bombs */ |
246 | 0 | #define OIDC_CJOSE_UNCOMPRESS_MAX (10 * 1024 * 1024) |
247 | | |
248 | | #ifdef USE_LIBBROTLI |
249 | | |
250 | | /* |
251 | | * deflate using libbrotli |
252 | | */ |
253 | | static apr_byte_t oidc_jose_brotli_compress(apr_pool_t *pool, const char *input, int input_len, char **output, |
254 | | int *output_len, oidc_jose_error_t *err) { |
255 | | size_t len = BrotliEncoderMaxCompressedSize(input_len); |
256 | | *output = apr_pcalloc(pool, len); |
257 | | if (BrotliEncoderCompress(BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_MODE_TEXT, input_len, |
258 | | (const uint8_t *)input, &len, (uint8_t *)*output) != BROTLI_TRUE) { |
259 | | oidc_jose_error(err, "BrotliEncoderCompress failed: compression error or buffer too small"); |
260 | | return FALSE; |
261 | | } |
262 | | *output_len = len; |
263 | | return TRUE; |
264 | | } |
265 | | |
266 | | /* Stream Brotli output into a doubling buffer so valid high-ratio payloads fit below the bomb cap. */ |
267 | | static apr_byte_t oidc_jose_brotli_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, |
268 | | int *output_len, oidc_jose_error_t *err) { |
269 | | apr_byte_t rv = FALSE; |
270 | | BrotliDecoderResult res = BROTLI_DECODER_RESULT_ERROR; |
271 | | size_t len = OIDC_CJOSE_UNCOMPRESS_CHUNK; |
272 | | size_t available_in = (size_t)input_len; |
273 | | const uint8_t *next_in = (const uint8_t *)input; |
274 | | size_t total_out = 0; |
275 | | char *buf = apr_pcalloc(pool, len); |
276 | | char *tmp = NULL; |
277 | | |
278 | | BrotliDecoderState *state = BrotliDecoderCreateInstance(NULL, NULL, NULL); |
279 | | if (state == NULL) { |
280 | | oidc_jose_error(err, "BrotliDecoderCreateInstance failed"); |
281 | | return FALSE; |
282 | | } |
283 | | |
284 | | while (1) { |
285 | | size_t available_out = len - total_out; |
286 | | uint8_t *next_out = (uint8_t *)(buf + total_out); |
287 | | res = BrotliDecoderDecompressStream(state, &available_in, &next_in, &available_out, &next_out, NULL); |
288 | | total_out = len - available_out; |
289 | | if (res == BROTLI_DECODER_RESULT_SUCCESS) |
290 | | break; |
291 | | if (res != BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { |
292 | | /* a decode error, or a truncated stream asking for more input */ |
293 | | oidc_jose_error(err, "BrotliDecoderDecompressStream failed: %d", res); |
294 | | goto end; |
295 | | } |
296 | | if (len >= OIDC_CJOSE_UNCOMPRESS_MAX) { |
297 | | oidc_jose_error(err, "brotli output would exceed %d bytes", OIDC_CJOSE_UNCOMPRESS_MAX); |
298 | | goto end; |
299 | | } |
300 | | /* grow by doubling, as the zlib side does and for the same reasons */ |
301 | | size_t next = (len > OIDC_CJOSE_UNCOMPRESS_MAX / 2) ? OIDC_CJOSE_UNCOMPRESS_MAX : len * 2; |
302 | | tmp = apr_pcalloc(pool, next); |
303 | | _oidc_memcpy(tmp, buf, len); |
304 | | len = next; |
305 | | buf = tmp; |
306 | | } |
307 | | |
308 | | *output = buf; |
309 | | *output_len = (int)total_out; |
310 | | rv = TRUE; |
311 | | |
312 | | end: |
313 | | |
314 | | BrotliDecoderDestroyInstance(state); |
315 | | |
316 | | return rv; |
317 | | } |
318 | | |
319 | | #endif |
320 | | |
321 | | #ifdef USE_ZLIB |
322 | | |
323 | | /* |
324 | | * deflate using zlib |
325 | | */ |
326 | | static apr_byte_t oidc_jose_zlib_compress(apr_pool_t *pool, const char *input, int input_len, char **output, |
327 | 0 | int *output_len, oidc_jose_error_t *err) { |
328 | 0 | apr_byte_t rv = FALSE; |
329 | 0 | int status = Z_OK; |
330 | 0 | z_stream zlib; |
331 | |
|
332 | 0 | zlib.zalloc = Z_NULL; |
333 | 0 | zlib.zfree = Z_NULL; |
334 | 0 | zlib.opaque = Z_NULL; |
335 | 0 | zlib.next_in = (Bytef *)input; |
336 | 0 | zlib.avail_in = input_len; |
337 | | |
338 | | /* |
339 | | * Small window and memory settings avoid oversized allocations for short payloads. The zlib |
340 | | * header records the window size, so the default inflate path remains compatible. |
341 | | */ |
342 | 0 | status = deflateInit2(&zlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 12, 5, Z_DEFAULT_STRATEGY); |
343 | 0 | if (status != Z_OK) { |
344 | 0 | oidc_jose_error(err, "deflateInit2() failed: %d", status); |
345 | 0 | goto end; |
346 | 0 | } |
347 | | |
348 | | /* deflateBound accounts for the zlib header/trailer overhead that dominates for |
349 | | * short inputs; a fixed input_len * 2 buffer made deflate fail on values of a |
350 | | * few bytes, silently preventing such values from being cached */ |
351 | | /* the (uInt) narrowing is safe: input_len is an int so deflateBound's small |
352 | | * relative overhead keeps the result well within uInt range */ |
353 | 0 | uLong output_max = deflateBound(&zlib, input_len); |
354 | 0 | *output = apr_pcalloc(pool, output_max); |
355 | 0 | zlib.next_out = (Bytef *)(*output); |
356 | 0 | zlib.avail_out = (uInt)output_max; |
357 | |
|
358 | 0 | status = deflate(&zlib, Z_FINISH); |
359 | 0 | if (status != Z_STREAM_END) { |
360 | 0 | oidc_jose_error(err, "deflate() failed: %d", status); |
361 | 0 | goto end; |
362 | 0 | } |
363 | | |
364 | 0 | *output_len = (int)zlib.total_out; |
365 | |
|
366 | 0 | rv = TRUE; |
367 | |
|
368 | 0 | end: |
369 | |
|
370 | 0 | deflateEnd(&zlib); |
371 | |
|
372 | 0 | return rv; |
373 | 0 | } |
374 | | |
375 | | /* |
376 | | * inflate using zlib |
377 | | */ |
378 | | static apr_byte_t oidc_jose_zlib_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, |
379 | 0 | int *output_len, apr_byte_t *capped, oidc_jose_error_t *err) { |
380 | 0 | apr_byte_t rv = FALSE; |
381 | 0 | int status = Z_OK; |
382 | 0 | size_t len = OIDC_CJOSE_UNCOMPRESS_CHUNK; |
383 | 0 | char *tmp = NULL; |
384 | 0 | char *buf = apr_pcalloc(pool, len); |
385 | 0 | z_stream zlib; |
386 | | |
387 | | /* whether inflation was stopped at the output cap: the one failure that must not be passed |
388 | | * through as "not a zlib stream after all" - see the caller */ |
389 | 0 | *capped = FALSE; |
390 | |
|
391 | 0 | zlib.zalloc = Z_NULL; |
392 | 0 | zlib.zfree = Z_NULL; |
393 | 0 | zlib.opaque = Z_NULL; |
394 | 0 | zlib.avail_in = (uInt)input_len; |
395 | 0 | zlib.next_in = (Bytef *)input; |
396 | 0 | zlib.total_out = 0; |
397 | |
|
398 | 0 | status = inflateInit(&zlib); |
399 | 0 | if (status != Z_OK) { |
400 | 0 | oidc_jose_error(err, "inflateInit() failed: %d", status); |
401 | 0 | goto end; |
402 | 0 | } |
403 | | |
404 | 0 | while (status == Z_OK) { |
405 | | /* Double pooled buffers; fixed growth retains old allocations and becomes quadratic. */ |
406 | 0 | if (zlib.total_out >= len) { |
407 | 0 | if (len >= OIDC_CJOSE_UNCOMPRESS_MAX) { |
408 | 0 | *capped = TRUE; |
409 | 0 | oidc_jose_error(err, "inflate() output would exceed %d bytes", |
410 | 0 | OIDC_CJOSE_UNCOMPRESS_MAX); |
411 | 0 | goto end; |
412 | 0 | } |
413 | 0 | size_t next = (len > OIDC_CJOSE_UNCOMPRESS_MAX / 2) ? OIDC_CJOSE_UNCOMPRESS_MAX : len * 2; |
414 | 0 | tmp = apr_pcalloc(pool, next); |
415 | 0 | _oidc_memcpy(tmp, buf, len); |
416 | 0 | len = next; |
417 | 0 | buf = tmp; |
418 | 0 | } |
419 | 0 | zlib.next_out = (Bytef *)(buf + zlib.total_out); |
420 | 0 | zlib.avail_out = (uInt)(len - zlib.total_out); |
421 | 0 | status = inflate(&zlib, Z_SYNC_FLUSH); |
422 | 0 | } |
423 | | |
424 | 0 | if (status != Z_STREAM_END) { |
425 | 0 | oidc_jose_error(err, "inflate() failed: %d", status); |
426 | 0 | goto end; |
427 | 0 | } |
428 | | |
429 | 0 | *output_len = (int)zlib.total_out; |
430 | 0 | *output = buf; |
431 | |
|
432 | 0 | rv = TRUE; |
433 | |
|
434 | 0 | end: |
435 | |
|
436 | 0 | inflateEnd(&zlib); |
437 | |
|
438 | 0 | return rv; |
439 | 0 | } |
440 | | |
441 | | #endif |
442 | | |
443 | | /* |
444 | | * compress using (compile-time) zlib or libbrotli, otherwise just plain copy |
445 | | */ |
446 | | apr_byte_t oidc_jose_compress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, |
447 | 0 | oidc_jose_error_t *err) { |
448 | | #ifdef USE_LIBBROTLI |
449 | | return oidc_jose_brotli_compress(pool, input, input_len, output, output_len, err); |
450 | | #elif defined(USE_ZLIB) |
451 | | return oidc_jose_zlib_compress(pool, input, input_len, output, output_len, err); |
452 | | #else |
453 | | *output = apr_pmemdup(pool, input, input_len); |
454 | | *output_len = input_len; |
455 | | return TRUE; |
456 | | #endif |
457 | 0 | } |
458 | | |
459 | | /* Recognize a possible zlib stream from its RFC 1950 header; callers handle false positives. */ |
460 | 0 | static apr_byte_t oidc_jose_is_zlib(const char *input, int input_len) { |
461 | 0 | const unsigned char *b = (const unsigned char *)input; |
462 | 0 | if ((input == NULL) || (input_len < 2)) |
463 | 0 | return FALSE; |
464 | 0 | if ((b[0] & 0x0f) != 8) |
465 | 0 | return FALSE; |
466 | 0 | return (((((unsigned int)b[0]) << 8) | b[1]) % 31) == 0 ? TRUE : FALSE; |
467 | 0 | } |
468 | | |
469 | | /* |
470 | | * Detect compression from the payload so caches remain readable across build or configuration |
471 | | * changes. A build without zlib still cannot decode a zlib payload. |
472 | | */ |
473 | | apr_byte_t oidc_jose_uncompress(apr_pool_t *pool, const char *input, int input_len, char **output, int *output_len, |
474 | 0 | oidc_jose_error_t *err) { |
475 | |
|
476 | 0 | if (oidc_jose_is_zlib(input, input_len)) { |
477 | 0 | #ifdef USE_ZLIB |
478 | | /* |
479 | | * The zlib header test has false positives for arbitrary cache values. Treat inflate |
480 | | * errors as uncompressed input; only the decompression-size cap is a hard failure. |
481 | | */ |
482 | 0 | apr_byte_t capped = FALSE; |
483 | 0 | if (oidc_jose_zlib_uncompress(pool, input, input_len, output, output_len, &capped, err) == TRUE) |
484 | 0 | return TRUE; |
485 | 0 | if (capped == TRUE) |
486 | 0 | return FALSE; |
487 | 0 | #endif |
488 | | /* Without zlib support, pass possible zlib input through for downstream validation. */ |
489 | 0 | } |
490 | | |
491 | | #ifdef USE_LIBBROTLI |
492 | | /* Brotli has no header; a failed decode may simply mean the payload is uncompressed. */ |
493 | | if (oidc_jose_brotli_uncompress(pool, input, input_len, output, output_len, err) == TRUE) |
494 | | return TRUE; |
495 | | #endif |
496 | | /* not compressed, or compressed by an algorithm this build does not have */ |
497 | 0 | *output = apr_pmemdup(pool, input, input_len); |
498 | 0 | *output_len = input_len; |
499 | 0 | return TRUE; |
500 | 0 | } |
501 | | |
502 | | #if (OPENSSL_VERSION_NUMBER < 0x10100000) || defined(LIBRESSL_VERSION_NUMBER) |
503 | | EVP_MD_CTX *EVP_MD_CTX_new() { |
504 | | return malloc(sizeof(EVP_MD_CTX)); |
505 | | } |
506 | | void EVP_MD_CTX_free(EVP_MD_CTX *ctx) { |
507 | | if (ctx) |
508 | | free(ctx); |
509 | | } |
510 | | #endif |
511 | | |
512 | 0 | #define OIDC_JOSE_CJOSE_VERSION_DEPRECATED "0.4." |
513 | | |
514 | | /* |
515 | | * return the version string of the underlying JOSE backend library |
516 | | */ |
517 | 0 | const char *oidc_jose_version(void) { |
518 | 0 | return cjose_version(); |
519 | 0 | } |
520 | | |
521 | | /* |
522 | | * check for a version of cjose < 0.5.0 that has a version of |
523 | | * cjose_jws_verify that resources after a verification failure |
524 | | */ |
525 | 0 | apr_byte_t oidc_jose_version_deprecated(apr_pool_t *pool) { |
526 | 0 | const char *version = apr_pstrdup(pool, cjose_version()); |
527 | 0 | return (_oidc_strstr(version, OIDC_JOSE_CJOSE_VERSION_DEPRECATED) == version); |
528 | 0 | } |
529 | | |
530 | | /* |
531 | | * hash a byte sequence with the specified algorithm |
532 | | */ |
533 | | apr_byte_t oidc_jose_hash_bytes(apr_pool_t *pool, const char *s_digest, const unsigned char *input, |
534 | | unsigned int input_len, unsigned char **output, unsigned int *output_len, |
535 | 3.26k | oidc_jose_error_t *err) { |
536 | 3.26k | apr_byte_t rv = FALSE; |
537 | 3.26k | unsigned char md_value[EVP_MAX_MD_SIZE]; |
538 | 3.26k | const EVP_MD *evp_digest = NULL; |
539 | 3.26k | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); |
540 | 3.26k | EVP_MD_CTX_init(ctx); |
541 | | |
542 | 3.26k | if ((evp_digest = EVP_get_digestbyname(s_digest)) == NULL) { |
543 | 0 | oidc_jose_error(err, "no OpenSSL digest algorithm found for algorithm \"%s\"", s_digest); |
544 | 0 | goto end; |
545 | 0 | } |
546 | | |
547 | 3.26k | if (!EVP_DigestInit_ex(ctx, evp_digest, NULL)) { |
548 | 0 | oidc_jose_error_openssl(err, "EVP_DigestInit_ex"); |
549 | 0 | goto end; |
550 | 0 | } |
551 | | |
552 | 3.26k | if (!EVP_DigestUpdate(ctx, input, input_len)) { |
553 | 0 | oidc_jose_error_openssl(err, "EVP_DigestUpdate"); |
554 | 0 | goto end; |
555 | 0 | } |
556 | | |
557 | 3.26k | if (!EVP_DigestFinal(ctx, md_value, output_len)) { |
558 | 0 | oidc_jose_error_openssl(err, "EVP_DigestFinal"); |
559 | 0 | goto end; |
560 | 0 | } |
561 | | |
562 | 3.26k | *output = apr_pmemdup(pool, md_value, *output_len); |
563 | | |
564 | 3.26k | rv = TRUE; |
565 | | |
566 | 3.26k | end: |
567 | | |
568 | 3.26k | if (ctx) |
569 | 3.26k | EVP_MD_CTX_free(ctx); |
570 | | |
571 | 3.26k | return rv; |
572 | 3.26k | } |
573 | | |
574 | | /* Map JOSE algorithms to digest name and length together; cjose names are not constant expressions. */ |
575 | 0 | static apr_byte_t _oidc_jose_alg_to_sha2(const char *alg, const char **openssl_digest, int *hash_len) { |
576 | 0 | const struct { |
577 | 0 | const char *algs[4]; |
578 | 0 | const char *openssl_digest; |
579 | 0 | int hash_len; |
580 | 0 | } families[] = { |
581 | 0 | {{CJOSE_HDR_ALG_RS256, CJOSE_HDR_ALG_PS256, CJOSE_HDR_ALG_HS256, CJOSE_HDR_ALG_ES256}, LN_sha256, 32}, |
582 | 0 | {{CJOSE_HDR_ALG_RS384, CJOSE_HDR_ALG_PS384, CJOSE_HDR_ALG_HS384, CJOSE_HDR_ALG_ES384}, LN_sha384, 48}, |
583 | 0 | {{CJOSE_HDR_ALG_RS512, CJOSE_HDR_ALG_PS512, CJOSE_HDR_ALG_HS512, CJOSE_HDR_ALG_ES512}, LN_sha512, 64}, |
584 | 0 | }; |
585 | 0 | for (int i = 0; i < (int)(sizeof(families) / sizeof(families[0])); i++) { |
586 | 0 | for (int j = 0; j < (int)(sizeof(families[0].algs) / sizeof(const char *)); j++) { |
587 | 0 | if (_oidc_strcmp(alg, families[i].algs[j]) == 0) { |
588 | 0 | *openssl_digest = families[i].openssl_digest; |
589 | 0 | *hash_len = families[i].hash_len; |
590 | 0 | return TRUE; |
591 | 0 | } |
592 | 0 | } |
593 | 0 | } |
594 | 0 | return FALSE; |
595 | 0 | } |
596 | | |
597 | | /* |
598 | | * return the OpenSSL hash algorithm associated with a specified JWT algorithm |
599 | | */ |
600 | 0 | static const char *oidc_jose_alg_to_openssl_digest(const char *alg) { |
601 | 0 | const char *digest = NULL; |
602 | 0 | int len = 0; |
603 | 0 | return _oidc_jose_alg_to_sha2(alg, &digest, &len) ? digest : NULL; |
604 | 0 | } |
605 | | |
606 | | /* |
607 | | * hash a string value with the specified algorithm |
608 | | */ |
609 | | apr_byte_t oidc_jose_hash_string(apr_pool_t *pool, const char *alg, const char *msg, char **hash, |
610 | 0 | unsigned int *hash_len, oidc_jose_error_t *err) { |
611 | |
|
612 | 0 | const char *s_digest = oidc_jose_alg_to_openssl_digest(alg); |
613 | 0 | if (s_digest == NULL) { |
614 | 0 | oidc_jose_error(err, "no OpenSSL digest algorithm name found for algorithm \"%s\"", alg); |
615 | 0 | return FALSE; |
616 | 0 | } |
617 | | |
618 | 0 | return oidc_jose_hash_bytes(pool, s_digest, (const unsigned char *)msg, (unsigned int)_oidc_strlen(msg), |
619 | 0 | (unsigned char **)hash, hash_len, err); |
620 | 0 | } |
621 | | |
622 | | /* |
623 | | * return hash length for the specified JOSE algorithm |
624 | | */ |
625 | 0 | int oidc_jose_hash_length(const char *alg) { |
626 | 0 | const char *digest = NULL; |
627 | 0 | int len = 0; |
628 | 0 | return _oidc_jose_alg_to_sha2(alg, &digest, &len) ? len : 0; |
629 | 0 | } |