blob: b901588dbf96b9286d97fc1e29234012ad5f3913 [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;
278 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200279 struct ebmb_node *eb;
280 struct jwt_cert_tree_entry *entry = NULL;
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100281 int is_ecdsa = 0;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200282
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200283 switch(ctx->alg) {
284 case JWS_ALG_RS256:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200285 evp = EVP_sha256();
286 break;
287 case JWS_ALG_RS384:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200288 evp = EVP_sha384();
289 break;
290 case JWS_ALG_RS512:
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100291 evp = EVP_sha512();
292 break;
293
294 case JWS_ALG_ES256:
295 evp = EVP_sha256();
296 is_ecdsa = 1;
297 break;
298 case JWS_ALG_ES384:
299 evp = EVP_sha384();
300 is_ecdsa = 1;
301 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200302 case JWS_ALG_ES512:
303 evp = EVP_sha512();
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100304 is_ecdsa = 1;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200305 break;
306 default: break;
307 }
308
309 evp_md_ctx = EVP_MD_CTX_new();
Tim Duesterhuse0c1d742021-10-18 18:40:29 +0200310 if (!evp_md_ctx)
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200311 return JWT_VRFY_OUT_OF_MEMORY;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200312
313 eb = ebst_lookup(&jwt_cert_tree, ctx->key);
314
315 if (!eb) {
316 retval = JWT_VRFY_UNKNOWN_CERT;
317 goto end;
318 }
319
320 entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
321
322 if (!entry->pkey) {
323 retval = JWT_VRFY_UNKNOWN_CERT;
324 goto end;
325 }
326
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100327 /*
328 * ECXXX signatures are a direct concatenation of the (R, S) pair and
329 * need to be converted back to asn.1 in order for verify operations to
330 * work with OpenSSL.
331 */
332 if (is_ecdsa) {
333 int conv_retval = convert_ecdsa_sig(ctx, entry->pkey, decoded_signature);
Remi Tricot-Le Bretona0658c32023-01-20 09:37:26 +0100334 if (conv_retval != 0) {
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100335 retval = conv_retval;
336 goto end;
337 }
338 }
339
340 if (EVP_DigestVerifyInit(evp_md_ctx, NULL, evp, NULL, entry->pkey) == 1 &&
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200341 EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
Remi Tricot-Le Breton5a8f02a2023-01-18 15:32:28 +0100342 ctx->jose.length + ctx->claims.length + 1) == 1 &&
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200343 EVP_DigestVerifyFinal(evp_md_ctx, (const unsigned char*)decoded_signature->area, decoded_signature->data) == 1) {
344 retval = JWT_VRFY_OK;
345 }
346
347end:
348 EVP_MD_CTX_free(evp_md_ctx);
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200349 return retval;
350}
351
352/*
353 * Check that the <token> that was signed via algorithm <alg> using the <key>
354 * (either an HMAC secret or the path to a public certificate) has a valid
355 * signature.
356 * Returns 1 in case of success.
357 */
358enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
359 const struct buffer *key)
360{
361 struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
362 unsigned int item_num = JWT_ELT_MAX;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200363 struct buffer *decoded_sig = NULL;
364 struct jwt_ctx ctx = {};
365 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Willy Tarreau468c0002021-10-15 11:41:16 +0200366 int ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200367
368 ctx.alg = jwt_parse_alg(alg->area, alg->data);
369
370 if (ctx.alg == JWT_ALG_DEFAULT)
371 return JWT_VRFY_UNKNOWN_ALG;
372
373 if (jwt_tokenize(token, items, &item_num))
374 return JWT_VRFY_INVALID_TOKEN;
375
376 if (item_num != JWT_ELT_MAX)
377 if (ctx.alg != JWS_ALG_NONE || item_num != JWT_ELT_SIG)
378 return JWT_VRFY_INVALID_TOKEN;
379
380 ctx.jose = items[JWT_ELT_JOSE];
381 ctx.claims = items[JWT_ELT_CLAIMS];
382 ctx.signature = items[JWT_ELT_SIG];
383
384 /* "alg" is "none", the signature must be empty for the JWS to be valid. */
385 if (ctx.alg == JWS_ALG_NONE) {
386 return (ctx.signature.length == 0) ? JWT_VRFY_OK : JWT_VRFY_KO;
387 }
388
389 if (ctx.signature.length == 0)
390 return JWT_VRFY_INVALID_TOKEN;
391
392 decoded_sig = alloc_trash_chunk();
393 if (!decoded_sig)
394 return JWT_VRFY_OUT_OF_MEMORY;
395
Willy Tarreau468c0002021-10-15 11:41:16 +0200396 ret = base64urldec(ctx.signature.start, ctx.signature.length,
397 decoded_sig->area, decoded_sig->size);
398 if (ret == -1) {
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200399 retval = JWT_VRFY_INVALID_TOKEN;
400 goto end;
401 }
402
Willy Tarreau468c0002021-10-15 11:41:16 +0200403 decoded_sig->data = ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200404 ctx.key = key->area;
405 ctx.key_length = key->data;
406
407 /* We have all three sections, signature calculation can begin. */
408
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200409 switch(ctx.alg) {
410
411 case JWS_ALG_HS256:
412 case JWS_ALG_HS384:
413 case JWS_ALG_HS512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200414 /* HMAC + SHA-XXX */
415 retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200416 break;
417 case JWS_ALG_RS256:
418 case JWS_ALG_RS384:
419 case JWS_ALG_RS512:
420 case JWS_ALG_ES256:
421 case JWS_ALG_ES384:
422 case JWS_ALG_ES512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200423 /* RSASSA-PKCS1-v1_5 + SHA-XXX */
424 /* ECDSA using P-XXX and SHA-XXX */
425 retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200426 break;
427 case JWS_ALG_PS256:
428 case JWS_ALG_PS384:
429 case JWS_ALG_PS512:
430 default:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200431 /* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
432
433 /* Not managed yet */
434 retval = JWT_VRFY_UNMANAGED_ALG;
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200435 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200436 }
437
438end:
439 free_trash_chunk(decoded_sig);
440
441 return retval;
442}
443
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200444static void jwt_deinit(void)
445{
446 struct ebmb_node *node = NULL;
447 struct jwt_cert_tree_entry *entry = NULL;
448
449 node = ebmb_first(&jwt_cert_tree);
450 while (node) {
451 entry = ebmb_entry(node, struct jwt_cert_tree_entry, node);
Remi Tricot-Le Breton4930c6c2022-02-04 14:06:34 +0100452 ebmb_delete(node);
Remi Tricot-Le Breton2b5a6552022-02-04 14:21:02 +0100453 EVP_PKEY_free(entry->pkey);
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200454 ha_free(&entry);
455 node = ebmb_first(&jwt_cert_tree);
456 }
457}
458REGISTER_POST_DEINIT(jwt_deinit);
459
460
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +0200461#endif /* USE_OPENSSL */