blob: aa5367ceba430766bee2c4ef5ee9ab384d172967 [file] [log] [blame]
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +02001/*
2 * JSON Web Token (JWT) processing
3 *
4 * Copyright 2021 HAProxy Technologies
5 * Remi Tricot-Le Breton <rlebreton@haproxy.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 */
12
13#include <import/ebmbtree.h>
14#include <import/ebsttree.h>
15
16#include <haproxy/api.h>
17#include <haproxy/tools.h>
18#include <haproxy/openssl-compat.h>
19#include <haproxy/base64.h>
20#include <haproxy/jwt.h>
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +010021#include <haproxy/buf.h>
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +020022
23
24#ifdef USE_OPENSSL
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +020025/* Tree into which the public certificates used to validate JWTs will be stored. */
26static struct eb_root jwt_cert_tree = EB_ROOT_UNIQUE;
27
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +020028/*
29 * The possible algorithm strings that can be found in a JWS's JOSE header are
30 * defined in section 3.1 of RFC7518.
31 */
32enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len)
33{
34 enum jwt_alg alg = JWT_ALG_DEFAULT;
35
36 /* Algorithms are all 5 characters long apart from "none". */
37 if (alg_len < sizeof("HS256")-1) {
Remi Tricot-Le Breton72663502021-11-03 12:23:54 +010038 if (alg_len == sizeof("none")-1 && strcmp("none", alg_str) == 0)
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +020039 alg = JWS_ALG_NONE;
40 return alg;
41 }
42
43 if (alg == JWT_ALG_DEFAULT) {
44 switch(*alg_str++) {
45 case 'H':
46 if (strncmp(alg_str, "S256", alg_len-1) == 0)
47 alg = JWS_ALG_HS256;
48 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
49 alg = JWS_ALG_HS384;
50 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
51 alg = JWS_ALG_HS512;
52 break;
53 case 'R':
54 if (strncmp(alg_str, "S256", alg_len-1) == 0)
55 alg = JWS_ALG_RS256;
56 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
57 alg = JWS_ALG_RS384;
58 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
59 alg = JWS_ALG_RS512;
60 break;
61 case 'E':
62 if (strncmp(alg_str, "S256", alg_len-1) == 0)
63 alg = JWS_ALG_ES256;
64 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
65 alg = JWS_ALG_ES384;
66 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
67 alg = JWS_ALG_ES512;
68 break;
69 case 'P':
70 if (strncmp(alg_str, "S256", alg_len-1) == 0)
71 alg = JWS_ALG_PS256;
72 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
73 alg = JWS_ALG_PS384;
74 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
75 alg = JWS_ALG_PS512;
76 break;
77 default:
78 break;
79 }
80 }
81
82 return alg;
83}
Remi Tricot-Le Bretone0d3c002021-10-01 15:36:55 +020084
Remi Tricot-Le Bretone0d3c002021-10-01 15:36:55 +020085/*
86 * Split a JWT into its separate dot-separated parts.
87 * Since only JWS following the Compact Serialization format are managed for
88 * now, we don't need to manage more than three subparts in the tokens.
89 * See section 3.1 of RFC7515 for more information about JWS Compact
90 * Serialization.
91 * Returns 0 in case of success.
92 */
93int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num)
94{
95 char *ptr = jwt->area;
96 char *jwt_end = jwt->area + jwt->data;
97 unsigned int index = 0;
98 unsigned int length = 0;
99
100 if (index < *item_num) {
101 items[index].start = ptr;
102 items[index].length = 0;
103 }
104
105 while (index < *item_num && ptr < jwt_end) {
106 if (*ptr++ == '.') {
107 items[index++].length = length;
108
109 if (index == *item_num)
110 return -1;
111 items[index].start = ptr;
112 items[index].length = 0;
113 length = 0;
114 } else
115 ++length;
116 }
117
118 if (index < *item_num)
119 items[index].length = length;
120
121 *item_num = (index+1);
122
123 return (ptr != jwt_end);
124}
125
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200126/*
127 * Parse a public certificate and insert it into the jwt_cert_tree.
128 * Returns 0 in case of success.
129 */
130int jwt_tree_load_cert(char *path, int pathlen, char **err)
131{
132 int retval = -1;
133 struct jwt_cert_tree_entry *entry = NULL;
134 EVP_PKEY *pkey = NULL;
135 BIO *bio = NULL;
136
Remi Tricot-Le Bretond544d332022-02-04 14:24:15 +0100137 entry = calloc(1, sizeof(*entry) + pathlen + 1);
138 if (!entry) {
139 memprintf(err, "%sunable to allocate memory (jwt_cert_tree_entry).\n", err && *err ? *err : "");
140 return -1;
141 }
142 memcpy(entry->path, path, pathlen + 1);
143
144 if (ebst_insert(&jwt_cert_tree, &entry->node) != &entry->node) {
145 free(entry);
146 return 0; /* Entry already in the tree */
147 }
148
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200149 bio = BIO_new(BIO_s_file());
150 if (!bio) {
151 memprintf(err, "%sunable to allocate memory (BIO).\n", err && *err ? *err : "");
152 goto end;
153 }
154
155 if (BIO_read_filename(bio, path) == 1) {
156
157 pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
158
159 if (!pkey) {
160 memprintf(err, "%sfile not found (%s)\n", err && *err ? *err : "", path);
161 goto end;
162 }
163
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200164 entry->pkey = pkey;
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200165 retval = 0;
166 }
167
168end:
Remi Tricot-Le Bretond544d332022-02-04 14:24:15 +0100169 if (retval) {
170 /* Some error happened during pkey parsing, remove the already
171 * inserted node from the tree and free it.
172 */
173 ebmb_delete(&entry->node);
174 free(entry);
175 }
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200176 BIO_free(bio);
177 return retval;
178}
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200179
180/*
181 * Calculate the HMAC signature of a specific JWT and check that it matches the
182 * one included in the token.
183 * Returns 1 in case of success.
184 */
185static enum jwt_vrfy_status
186jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signature)
187{
188 const EVP_MD *evp = NULL;
Tim Duesterhusc87d3c22021-10-18 18:40:28 +0200189 unsigned char signature[EVP_MAX_MD_SIZE];
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200190 unsigned int signature_length = 0;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200191 unsigned char *hmac_res = NULL;
192 enum jwt_vrfy_status retval = JWT_VRFY_KO;
193
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200194 switch(ctx->alg) {
195 case JWS_ALG_HS256:
196 evp = EVP_sha256();
197 break;
198 case JWS_ALG_HS384:
199 evp = EVP_sha384();
200 break;
201 case JWS_ALG_HS512:
202 evp = EVP_sha512();
203 break;
204 default: break;
205 }
206
207 hmac_res = HMAC(evp, ctx->key, ctx->key_length, (const unsigned char*)ctx->jose.start,
208 ctx->jose.length + ctx->claims.length + 1, signature, &signature_length);
209
210 if (hmac_res && signature_length == decoded_signature->data &&
Willy Tarreauce16db42021-10-15 11:52:41 +0200211 (CRYPTO_memcmp(decoded_signature->area, signature, signature_length) == 0))
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200212 retval = JWT_VRFY_OK;
213
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200214 return retval;
215}
216
217/*
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100218 * Convert a JWT ECDSA signature (R and S parameters concatenatedi, see section
219 * 3.4 of RFC7518) into an ECDSA_SIG that can be fed back into OpenSSL's digest
220 * verification functions.
221 * Returns 0 in case of success.
222 */
223static int convert_ecdsa_sig(const struct jwt_ctx *ctx, EVP_PKEY *pkey, struct buffer *signature)
224{
225 int retval = 0;
226 ECDSA_SIG *ecdsa_sig = NULL;
227 BIGNUM *ec_R = NULL, *ec_S = NULL;
228 unsigned int bignum_len;
229 unsigned char *p;
230
231 ecdsa_sig = ECDSA_SIG_new();
232 if (!ecdsa_sig) {
233 retval = JWT_VRFY_OUT_OF_MEMORY;
234 goto end;
235 }
236
237 if (b_data(signature) % 2) {
238 retval = JWT_VRFY_INVALID_TOKEN;
239 goto end;
240 }
241
242 bignum_len = b_data(signature) / 2;
243
244 ec_R = BN_bin2bn((unsigned char*)b_orig(signature), bignum_len, NULL);
245 ec_S = BN_bin2bn((unsigned char *)(b_orig(signature) + bignum_len), bignum_len, NULL);
246
247 if (!ec_R || !ec_S) {
248 retval = JWT_VRFY_INVALID_TOKEN;
249 goto end;
250 }
251
252 /* Build ecdsa out of R and S values. */
253 ECDSA_SIG_set0(ecdsa_sig, ec_R, ec_S);
254
255 p = (unsigned char*)signature->area;
256
257 signature->data = i2d_ECDSA_SIG(ecdsa_sig, &p);
258 if (signature->data == 0) {
259 retval = JWT_VRFY_INVALID_TOKEN;
260 goto end;
261 }
262
263end:
264 ECDSA_SIG_free(ecdsa_sig);
265 return retval;
266}
267
268/*
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200269 * Check that the signature included in a JWT signed via RSA or ECDSA is valid
270 * and can be verified thanks to a given public certificate.
271 * Returns 1 in case of success.
272 */
273static enum jwt_vrfy_status
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100274jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, struct buffer *decoded_signature)
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200275{
276 const EVP_MD *evp = NULL;
277 EVP_MD_CTX *evp_md_ctx;
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100278 EVP_PKEY_CTX *pkey_ctx = NULL;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200279 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200280 struct ebmb_node *eb;
281 struct jwt_cert_tree_entry *entry = NULL;
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100282 int is_ecdsa = 0;
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100283 int padding = RSA_PKCS1_PADDING;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200284
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200285 switch(ctx->alg) {
286 case JWS_ALG_RS256:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200287 evp = EVP_sha256();
288 break;
289 case JWS_ALG_RS384:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200290 evp = EVP_sha384();
291 break;
292 case JWS_ALG_RS512:
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100293 evp = EVP_sha512();
294 break;
295
296 case JWS_ALG_ES256:
297 evp = EVP_sha256();
298 is_ecdsa = 1;
299 break;
300 case JWS_ALG_ES384:
301 evp = EVP_sha384();
302 is_ecdsa = 1;
303 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200304 case JWS_ALG_ES512:
305 evp = EVP_sha512();
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100306 is_ecdsa = 1;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200307 break;
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100308
309 case JWS_ALG_PS256:
310 evp = EVP_sha256();
311 padding = RSA_PKCS1_PSS_PADDING;
312 break;
313 case JWS_ALG_PS384:
314 evp = EVP_sha384();
315 padding = RSA_PKCS1_PSS_PADDING;
316 break;
317 case JWS_ALG_PS512:
318 evp = EVP_sha512();
319 padding = RSA_PKCS1_PSS_PADDING;
320 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200321 default: break;
322 }
323
324 evp_md_ctx = EVP_MD_CTX_new();
Tim Duesterhuse0c1d742021-10-18 18:40:29 +0200325 if (!evp_md_ctx)
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200326 return JWT_VRFY_OUT_OF_MEMORY;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200327
328 eb = ebst_lookup(&jwt_cert_tree, ctx->key);
329
330 if (!eb) {
331 retval = JWT_VRFY_UNKNOWN_CERT;
332 goto end;
333 }
334
335 entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
336
337 if (!entry->pkey) {
338 retval = JWT_VRFY_UNKNOWN_CERT;
339 goto end;
340 }
341
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100342 /*
343 * ECXXX signatures are a direct concatenation of the (R, S) pair and
344 * need to be converted back to asn.1 in order for verify operations to
345 * work with OpenSSL.
346 */
347 if (is_ecdsa) {
348 int conv_retval = convert_ecdsa_sig(ctx, entry->pkey, decoded_signature);
Remi Tricot-Le Bretona0658c32023-01-20 09:37:26 +0100349 if (conv_retval != 0) {
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100350 retval = conv_retval;
351 goto end;
352 }
353 }
354
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100355 if (EVP_DigestVerifyInit(evp_md_ctx, &pkey_ctx, evp, NULL, entry->pkey) == 1) {
356 if (is_ecdsa || EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, padding) > 0) {
357 if (EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
358 ctx->jose.length + ctx->claims.length + 1) == 1 &&
359 EVP_DigestVerifyFinal(evp_md_ctx, (const unsigned char*)decoded_signature->area, decoded_signature->data) == 1) {
360 retval = JWT_VRFY_OK;
361 }
362 }
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200363 }
364
365end:
366 EVP_MD_CTX_free(evp_md_ctx);
Christopher Faulet4e2d1b22024-07-26 16:47:15 +0200367 if (retval != JWT_VRFY_OK) {
368 /* Don't forget to remove SSL errors to be sure they cannot be
369 * caught elsewhere. The error queue is cleared because it seems
370 * at least 2 errors are produced.
371 */
372 ERR_clear_error();
373 }
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200374 return retval;
375}
376
377/*
378 * Check that the <token> that was signed via algorithm <alg> using the <key>
379 * (either an HMAC secret or the path to a public certificate) has a valid
380 * signature.
381 * Returns 1 in case of success.
382 */
383enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
384 const struct buffer *key)
385{
386 struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
387 unsigned int item_num = JWT_ELT_MAX;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200388 struct buffer *decoded_sig = NULL;
389 struct jwt_ctx ctx = {};
390 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Willy Tarreau468c0002021-10-15 11:41:16 +0200391 int ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200392
393 ctx.alg = jwt_parse_alg(alg->area, alg->data);
394
395 if (ctx.alg == JWT_ALG_DEFAULT)
396 return JWT_VRFY_UNKNOWN_ALG;
397
398 if (jwt_tokenize(token, items, &item_num))
399 return JWT_VRFY_INVALID_TOKEN;
400
401 if (item_num != JWT_ELT_MAX)
402 if (ctx.alg != JWS_ALG_NONE || item_num != JWT_ELT_SIG)
403 return JWT_VRFY_INVALID_TOKEN;
404
405 ctx.jose = items[JWT_ELT_JOSE];
406 ctx.claims = items[JWT_ELT_CLAIMS];
407 ctx.signature = items[JWT_ELT_SIG];
408
409 /* "alg" is "none", the signature must be empty for the JWS to be valid. */
410 if (ctx.alg == JWS_ALG_NONE) {
411 return (ctx.signature.length == 0) ? JWT_VRFY_OK : JWT_VRFY_KO;
412 }
413
414 if (ctx.signature.length == 0)
415 return JWT_VRFY_INVALID_TOKEN;
416
417 decoded_sig = alloc_trash_chunk();
418 if (!decoded_sig)
419 return JWT_VRFY_OUT_OF_MEMORY;
420
Willy Tarreau468c0002021-10-15 11:41:16 +0200421 ret = base64urldec(ctx.signature.start, ctx.signature.length,
422 decoded_sig->area, decoded_sig->size);
423 if (ret == -1) {
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200424 retval = JWT_VRFY_INVALID_TOKEN;
425 goto end;
426 }
427
Willy Tarreau468c0002021-10-15 11:41:16 +0200428 decoded_sig->data = ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200429 ctx.key = key->area;
430 ctx.key_length = key->data;
431
432 /* We have all three sections, signature calculation can begin. */
433
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200434 switch(ctx.alg) {
435
436 case JWS_ALG_HS256:
437 case JWS_ALG_HS384:
438 case JWS_ALG_HS512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200439 /* HMAC + SHA-XXX */
440 retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200441 break;
442 case JWS_ALG_RS256:
443 case JWS_ALG_RS384:
444 case JWS_ALG_RS512:
445 case JWS_ALG_ES256:
446 case JWS_ALG_ES384:
447 case JWS_ALG_ES512:
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100448 case JWS_ALG_PS256:
449 case JWS_ALG_PS384:
450 case JWS_ALG_PS512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200451 /* RSASSA-PKCS1-v1_5 + SHA-XXX */
452 /* ECDSA using P-XXX and SHA-XXX */
Remi Tricot-Le Breton447a38f2023-03-07 17:43:57 +0100453 /* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200454 retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200455 break;
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200456 default:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200457 /* Not managed yet */
458 retval = JWT_VRFY_UNMANAGED_ALG;
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200459 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200460 }
461
462end:
463 free_trash_chunk(decoded_sig);
464
465 return retval;
466}
467
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200468static void jwt_deinit(void)
469{
470 struct ebmb_node *node = NULL;
471 struct jwt_cert_tree_entry *entry = NULL;
472
473 node = ebmb_first(&jwt_cert_tree);
474 while (node) {
475 entry = ebmb_entry(node, struct jwt_cert_tree_entry, node);
Remi Tricot-Le Breton4930c6c2022-02-04 14:06:34 +0100476 ebmb_delete(node);
Remi Tricot-Le Breton2b5a6552022-02-04 14:21:02 +0100477 EVP_PKEY_free(entry->pkey);
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200478 ha_free(&entry);
479 node = ebmb_first(&jwt_cert_tree);
480 }
481}
482REGISTER_POST_DEINIT(jwt_deinit);
483
484
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +0200485#endif /* USE_OPENSSL */