blob: 7f20e374b6f888684c25a2dd1ecdedf8caeede18 [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>
21
22
23#ifdef USE_OPENSSL
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +020024/* Tree into which the public certificates used to validate JWTs will be stored. */
25static struct eb_root jwt_cert_tree = EB_ROOT_UNIQUE;
26
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +020027/*
28 * The possible algorithm strings that can be found in a JWS's JOSE header are
29 * defined in section 3.1 of RFC7518.
30 */
31enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len)
32{
33 enum jwt_alg alg = JWT_ALG_DEFAULT;
34
35 /* Algorithms are all 5 characters long apart from "none". */
36 if (alg_len < sizeof("HS256")-1) {
Remi Tricot-Le Breton72663502021-11-03 12:23:54 +010037 if (alg_len == sizeof("none")-1 && strcmp("none", alg_str) == 0)
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +020038 alg = JWS_ALG_NONE;
39 return alg;
40 }
41
42 if (alg == JWT_ALG_DEFAULT) {
43 switch(*alg_str++) {
44 case 'H':
45 if (strncmp(alg_str, "S256", alg_len-1) == 0)
46 alg = JWS_ALG_HS256;
47 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
48 alg = JWS_ALG_HS384;
49 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
50 alg = JWS_ALG_HS512;
51 break;
52 case 'R':
53 if (strncmp(alg_str, "S256", alg_len-1) == 0)
54 alg = JWS_ALG_RS256;
55 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
56 alg = JWS_ALG_RS384;
57 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
58 alg = JWS_ALG_RS512;
59 break;
60 case 'E':
61 if (strncmp(alg_str, "S256", alg_len-1) == 0)
62 alg = JWS_ALG_ES256;
63 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
64 alg = JWS_ALG_ES384;
65 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
66 alg = JWS_ALG_ES512;
67 break;
68 case 'P':
69 if (strncmp(alg_str, "S256", alg_len-1) == 0)
70 alg = JWS_ALG_PS256;
71 else if (strncmp(alg_str, "S384", alg_len-1) == 0)
72 alg = JWS_ALG_PS384;
73 else if (strncmp(alg_str, "S512", alg_len-1) == 0)
74 alg = JWS_ALG_PS512;
75 break;
76 default:
77 break;
78 }
79 }
80
81 return alg;
82}
Remi Tricot-Le Bretone0d3c002021-10-01 15:36:55 +020083
Remi Tricot-Le Bretone0d3c002021-10-01 15:36:55 +020084/*
85 * Split a JWT into its separate dot-separated parts.
86 * Since only JWS following the Compact Serialization format are managed for
87 * now, we don't need to manage more than three subparts in the tokens.
88 * See section 3.1 of RFC7515 for more information about JWS Compact
89 * Serialization.
90 * Returns 0 in case of success.
91 */
92int jwt_tokenize(const struct buffer *jwt, struct jwt_item *items, unsigned int *item_num)
93{
94 char *ptr = jwt->area;
95 char *jwt_end = jwt->area + jwt->data;
96 unsigned int index = 0;
97 unsigned int length = 0;
98
99 if (index < *item_num) {
100 items[index].start = ptr;
101 items[index].length = 0;
102 }
103
104 while (index < *item_num && ptr < jwt_end) {
105 if (*ptr++ == '.') {
106 items[index++].length = length;
107
108 if (index == *item_num)
109 return -1;
110 items[index].start = ptr;
111 items[index].length = 0;
112 length = 0;
113 } else
114 ++length;
115 }
116
117 if (index < *item_num)
118 items[index].length = length;
119
120 *item_num = (index+1);
121
122 return (ptr != jwt_end);
123}
124
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200125/*
126 * Parse a public certificate and insert it into the jwt_cert_tree.
127 * Returns 0 in case of success.
128 */
129int jwt_tree_load_cert(char *path, int pathlen, char **err)
130{
131 int retval = -1;
132 struct jwt_cert_tree_entry *entry = NULL;
133 EVP_PKEY *pkey = NULL;
134 BIO *bio = NULL;
135
Remi Tricot-Le Bretond544d332022-02-04 14:24:15 +0100136 entry = calloc(1, sizeof(*entry) + pathlen + 1);
137 if (!entry) {
138 memprintf(err, "%sunable to allocate memory (jwt_cert_tree_entry).\n", err && *err ? *err : "");
139 return -1;
140 }
141 memcpy(entry->path, path, pathlen + 1);
142
143 if (ebst_insert(&jwt_cert_tree, &entry->node) != &entry->node) {
144 free(entry);
145 return 0; /* Entry already in the tree */
146 }
147
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200148 bio = BIO_new(BIO_s_file());
149 if (!bio) {
150 memprintf(err, "%sunable to allocate memory (BIO).\n", err && *err ? *err : "");
151 goto end;
152 }
153
154 if (BIO_read_filename(bio, path) == 1) {
155
156 pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
157
158 if (!pkey) {
159 memprintf(err, "%sfile not found (%s)\n", err && *err ? *err : "", path);
160 goto end;
161 }
162
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200163 entry->pkey = pkey;
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200164 retval = 0;
165 }
166
167end:
Remi Tricot-Le Bretond544d332022-02-04 14:24:15 +0100168 if (retval) {
169 /* Some error happened during pkey parsing, remove the already
170 * inserted node from the tree and free it.
171 */
172 ebmb_delete(&entry->node);
173 free(entry);
174 }
Remi Tricot-Le Breton864089e2021-10-01 15:36:56 +0200175 BIO_free(bio);
176 return retval;
177}
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200178
179/*
180 * Calculate the HMAC signature of a specific JWT and check that it matches the
181 * one included in the token.
182 * Returns 1 in case of success.
183 */
184static enum jwt_vrfy_status
185jwt_jwsverify_hmac(const struct jwt_ctx *ctx, const struct buffer *decoded_signature)
186{
187 const EVP_MD *evp = NULL;
Tim Duesterhusc87d3c22021-10-18 18:40:28 +0200188 unsigned char signature[EVP_MAX_MD_SIZE];
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200189 unsigned int signature_length = 0;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200190 unsigned char *hmac_res = NULL;
191 enum jwt_vrfy_status retval = JWT_VRFY_KO;
192
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200193 switch(ctx->alg) {
194 case JWS_ALG_HS256:
195 evp = EVP_sha256();
196 break;
197 case JWS_ALG_HS384:
198 evp = EVP_sha384();
199 break;
200 case JWS_ALG_HS512:
201 evp = EVP_sha512();
202 break;
203 default: break;
204 }
205
206 hmac_res = HMAC(evp, ctx->key, ctx->key_length, (const unsigned char*)ctx->jose.start,
207 ctx->jose.length + ctx->claims.length + 1, signature, &signature_length);
208
209 if (hmac_res && signature_length == decoded_signature->data &&
Willy Tarreauce16db42021-10-15 11:52:41 +0200210 (CRYPTO_memcmp(decoded_signature->area, signature, signature_length) == 0))
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200211 retval = JWT_VRFY_OK;
212
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200213 return retval;
214}
215
216/*
217 * Check that the signature included in a JWT signed via RSA or ECDSA is valid
218 * and can be verified thanks to a given public certificate.
219 * Returns 1 in case of success.
220 */
221static enum jwt_vrfy_status
222jwt_jwsverify_rsa_ecdsa(const struct jwt_ctx *ctx, const struct buffer *decoded_signature)
223{
224 const EVP_MD *evp = NULL;
225 EVP_MD_CTX *evp_md_ctx;
226 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200227 struct ebmb_node *eb;
228 struct jwt_cert_tree_entry *entry = NULL;
229
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200230 switch(ctx->alg) {
231 case JWS_ALG_RS256:
232 case JWS_ALG_ES256:
233 evp = EVP_sha256();
234 break;
235 case JWS_ALG_RS384:
236 case JWS_ALG_ES384:
237 evp = EVP_sha384();
238 break;
239 case JWS_ALG_RS512:
240 case JWS_ALG_ES512:
241 evp = EVP_sha512();
242 break;
243 default: break;
244 }
245
246 evp_md_ctx = EVP_MD_CTX_new();
Tim Duesterhuse0c1d742021-10-18 18:40:29 +0200247 if (!evp_md_ctx)
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200248 return JWT_VRFY_OUT_OF_MEMORY;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200249
250 eb = ebst_lookup(&jwt_cert_tree, ctx->key);
251
252 if (!eb) {
253 retval = JWT_VRFY_UNKNOWN_CERT;
254 goto end;
255 }
256
257 entry = ebmb_entry(eb, struct jwt_cert_tree_entry, node);
258
259 if (!entry->pkey) {
260 retval = JWT_VRFY_UNKNOWN_CERT;
261 goto end;
262 }
263
264 if (EVP_DigestVerifyInit(evp_md_ctx, NULL, evp, NULL,entry-> pkey) == 1 &&
265 EVP_DigestVerifyUpdate(evp_md_ctx, (const unsigned char*)ctx->jose.start,
266 ctx->jose.length + ctx->claims.length + 1) == 1 &&
267 EVP_DigestVerifyFinal(evp_md_ctx, (const unsigned char*)decoded_signature->area, decoded_signature->data) == 1) {
268 retval = JWT_VRFY_OK;
269 }
270
271end:
272 EVP_MD_CTX_free(evp_md_ctx);
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200273 return retval;
274}
275
276/*
277 * Check that the <token> that was signed via algorithm <alg> using the <key>
278 * (either an HMAC secret or the path to a public certificate) has a valid
279 * signature.
280 * Returns 1 in case of success.
281 */
282enum jwt_vrfy_status jwt_verify(const struct buffer *token, const struct buffer *alg,
283 const struct buffer *key)
284{
285 struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
286 unsigned int item_num = JWT_ELT_MAX;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200287 struct buffer *decoded_sig = NULL;
288 struct jwt_ctx ctx = {};
289 enum jwt_vrfy_status retval = JWT_VRFY_KO;
Willy Tarreau468c0002021-10-15 11:41:16 +0200290 int ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200291
292 ctx.alg = jwt_parse_alg(alg->area, alg->data);
293
294 if (ctx.alg == JWT_ALG_DEFAULT)
295 return JWT_VRFY_UNKNOWN_ALG;
296
297 if (jwt_tokenize(token, items, &item_num))
298 return JWT_VRFY_INVALID_TOKEN;
299
300 if (item_num != JWT_ELT_MAX)
301 if (ctx.alg != JWS_ALG_NONE || item_num != JWT_ELT_SIG)
302 return JWT_VRFY_INVALID_TOKEN;
303
304 ctx.jose = items[JWT_ELT_JOSE];
305 ctx.claims = items[JWT_ELT_CLAIMS];
306 ctx.signature = items[JWT_ELT_SIG];
307
308 /* "alg" is "none", the signature must be empty for the JWS to be valid. */
309 if (ctx.alg == JWS_ALG_NONE) {
310 return (ctx.signature.length == 0) ? JWT_VRFY_OK : JWT_VRFY_KO;
311 }
312
313 if (ctx.signature.length == 0)
314 return JWT_VRFY_INVALID_TOKEN;
315
316 decoded_sig = alloc_trash_chunk();
317 if (!decoded_sig)
318 return JWT_VRFY_OUT_OF_MEMORY;
319
Willy Tarreau468c0002021-10-15 11:41:16 +0200320 ret = base64urldec(ctx.signature.start, ctx.signature.length,
321 decoded_sig->area, decoded_sig->size);
322 if (ret == -1) {
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200323 retval = JWT_VRFY_INVALID_TOKEN;
324 goto end;
325 }
326
Willy Tarreau468c0002021-10-15 11:41:16 +0200327 decoded_sig->data = ret;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200328 ctx.key = key->area;
329 ctx.key_length = key->data;
330
331 /* We have all three sections, signature calculation can begin. */
332
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200333 switch(ctx.alg) {
334
335 case JWS_ALG_HS256:
336 case JWS_ALG_HS384:
337 case JWS_ALG_HS512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200338 /* HMAC + SHA-XXX */
339 retval = jwt_jwsverify_hmac(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200340 break;
341 case JWS_ALG_RS256:
342 case JWS_ALG_RS384:
343 case JWS_ALG_RS512:
344 case JWS_ALG_ES256:
345 case JWS_ALG_ES384:
346 case JWS_ALG_ES512:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200347 /* RSASSA-PKCS1-v1_5 + SHA-XXX */
348 /* ECDSA using P-XXX and SHA-XXX */
349 retval = jwt_jwsverify_rsa_ecdsa(&ctx, decoded_sig);
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200350 break;
351 case JWS_ALG_PS256:
352 case JWS_ALG_PS384:
353 case JWS_ALG_PS512:
354 default:
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200355 /* RSASSA-PSS using SHA-XXX and MGF1 with SHA-XXX */
356
357 /* Not managed yet */
358 retval = JWT_VRFY_UNMANAGED_ALG;
Remi Tricot-Le Breton8abed172021-10-18 15:14:48 +0200359 break;
Remi Tricot-Le Breton130e1422021-10-01 15:36:58 +0200360 }
361
362end:
363 free_trash_chunk(decoded_sig);
364
365 return retval;
366}
367
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200368static void jwt_deinit(void)
369{
370 struct ebmb_node *node = NULL;
371 struct jwt_cert_tree_entry *entry = NULL;
372
373 node = ebmb_first(&jwt_cert_tree);
374 while (node) {
375 entry = ebmb_entry(node, struct jwt_cert_tree_entry, node);
Remi Tricot-Le Breton4930c6c2022-02-04 14:06:34 +0100376 ebmb_delete(node);
Remi Tricot-Le Breton2b5a6552022-02-04 14:21:02 +0100377 EVP_PKEY_free(entry->pkey);
Remi Tricot-Le Breton0b24d2f2021-10-18 15:14:47 +0200378 ha_free(&entry);
379 node = ebmb_first(&jwt_cert_tree);
380 }
381}
382REGISTER_POST_DEINIT(jwt_deinit);
383
384
Remi Tricot-Le Breton7feb3612021-10-01 15:36:54 +0200385#endif /* USE_OPENSSL */