1 /* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
2
3 #include "lib.h"
4 #include "buffer.h"
5 #include "str.h"
6 #include "ostream.h"
7 #include "hmac.h"
8 #include "sha2.h"
9 #include "base64.h"
10 #include "randgen.h"
11 #include "array.h"
12 #include "json-parser.h"
13 #include "iso8601-date.h"
14 #include "oauth2.h"
15 #include "oauth2-private.h"
16 #include "dcrypt.h"
17 #include "dict.h"
18 #include "dict-private.h"
19 #include "test-common.h"
20 #include "unlink-directory.h"
21
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 #define base64url_encode_str(str, dest) \
26 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX, (str), \
27 strlen((str)), (dest))
28
29 /**
30 * Test keypair used only for this test.
31 */
32 static const char *rsa_public_key =
33 "-----BEGIN PUBLIC KEY-----\n"
34 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\n"
35 "vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\n"
36 "aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\n"
37 "tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\n"
38 "e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\n"
39 "V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\n"
40 "MwIDAQAB\n"
41 "-----END PUBLIC KEY-----";
42 static const char *rsa_private_key =
43 "-----BEGIN PRIVATE KEY-----\n"
44 "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfPKKzVmN80HRs\n"
45 "GAoUxK++RO3CW8GxomrtLnAD6TN5U5WlVbCRZ1WFrizfxcz+lr/Kvjtq/v7PdVOa\n"
46 "8NHIAdxpP3bCFEQWku/1yPmVN4lKJvKv8yub9i2MJlVaBo5giHCtfAouo+v/XWKd\n"
47 "awCR8jK28dZPFlgRxcuABcW5S5pLe4X2ASI1DDMZNTW/QWqSpMGvgHydbccI3jtd\n"
48 "S7S3xjR76V/izg7FBrBYPv0n3/l3dHLS9tXcCbUW0YmIm87BGwh9UKEOlhK1NwdM\n"
49 "Iyq29ZtXovXUFaSnMZdJbge/jepr4ZJg4PZBTrwxvn2hKTY4H4G04ukmh+ZsYQaC\n"
50 "+bDIIj0zAgMBAAECggEAKIBGrbCSW2O1yOyQW9nvDUkA5EdsS58Q7US7bvM4iWpu\n"
51 "DIBwCXur7/VuKnhn/HUhURLzj/JNozynSChqYyG+CvL+ZLy82LUE3ZIBkSdv/vFL\n"
52 "Ft+VvvRtf1EcsmoqenkZl7aN7HD7DJeXBoz5tyVQKuH17WW0fsi9StGtCcUl+H6K\n"
53 "zV9Gif0Kj0uLQbCg3THRvKuueBTwCTdjoP0PwaNADgSWb3hJPeLMm/yII4tIMGbO\n"
54 "w+xd9wJRl+ZN9nkNtQMxszFGdKjedB6goYLQuP0WRZx+YtykaVJdM75bDUvsQar4\n"
55 "9Pc21Fp7UVk/CN11DX/hX3TmTJAUtqYADliVKkTbCQKBgQDLU48tBxm3g1CdDM/P\n"
56 "ZIEmpA3Y/m7e9eX7M1Uo/zDh4G/S9a4kkX6GQY2dLFdCtOS8M4hR11Io7MceBKDi\n"
57 "djorTZ5zJPQ8+b9Rm+1GlaucGNwRW0cQk2ltT2ksPmJnQn2xvM9T8vE+a4A/YGzw\n"
58 "mZOfpoVGykWs/tbSzU2aTaOybQKBgQDIfRf6OmirGPh59l+RSuDkZtISF/51mCV/\n"
59 "S1M4DltWDwhjC2Y2T+meIsb/Mjtz4aVNz0EHB8yvn0TMGr94Uwjv4uBdpVSwz+xL\n"
60 "hHL7J4rpInH+i0gxa0N+rGwsPwI8wJG95wLY+Kni5KCuXQw55uX1cqnnsahpRZFZ\n"
61 "EerBXhjqHwKBgBmEjiaHipm2eEqNjhMoOPFBi59dJ0sCL2/cXGa9yEPA6Cfgv49F\n"
62 "V0zAM2azZuwvSbm4+fXTgTMzrDW/PPXPArPmlOk8jQ6OBY3XdOrz48q+b/gZrYyO\n"
63 "A6A9ZCSyW6U7+gxxds/BYLeFxF2v21xC2f0iZ/2faykv/oQMUh34en/tAoGACqVZ\n"
64 "2JexZyR0TUWf3X80YexzyzIq+OOTWicNzDQ29WLm9xtr2gZ0SUlfd72bGpQoyvDu\n"
65 "awkm/UxfwtbIxALkvpg1gcN9s8XWrkviLyPyZF7H3tRWiQlBFEDjnZXa8I7pLkRO\n"
66 "Cmdp3fp17cxTEeAI5feovfzZDH39MdWZuZrdh9ECgYBTEv8S7nK8wrxIC390kroV\n"
67 "52eBwzckQU2mWa0thUtaGQiU1EYPCSDcjkrLXwB72ft0dW57KyWtvrB6rt1ORgOL\n"
68 "eI5hFbwdGQhCHTrAR1vG3SyFPMAm+8JB+sGOD/fvjtZKx//MFNweKFNEF0C/o6Z2\n"
69 "FXj90PlgF8sCQut36ZfuIQ==\n"
70 "-----END PRIVATE KEY-----";
71
72 static buffer_t *hs_sign_key = NULL;
73
74 static struct dict *keys_dict = NULL;
75
76 static bool skip_dcrypt = FALSE;
77
78 static struct oauth2_validation_key_cache *key_cache = NULL;
79
parse_jwt_token(struct oauth2_request * req,const char * token,bool * is_jwt_r,const char ** error_r)80 static int parse_jwt_token(struct oauth2_request *req, const char *token,
81 bool *is_jwt_r, const char **error_r)
82 {
83 struct oauth2_settings set;
84
85 i_zero(&set);
86 set.scope = "mail";
87 set.key_dict = keys_dict;
88 set.key_cache = key_cache;
89 i_zero(req);
90 req->pool = pool_datastack_create();
91 req->set = &set;
92 t_array_init(&req->fields, 8);
93 return oauth2_try_parse_jwt(&set, token, &req->fields, is_jwt_r,
94 error_r);
95 }
96
test_jwt_token(const char * token)97 static void test_jwt_token(const char *token)
98 {
99 /* then see what the parser likes it */
100 struct oauth2_request req;
101 const char *error = NULL;
102
103 bool is_jwt;
104 test_assert(parse_jwt_token(&req, token, &is_jwt, &error) == 0);
105 test_assert(is_jwt == TRUE);
106 test_assert(error == NULL);
107
108 /* check fields */
109 test_assert(array_is_created(&req.fields));
110 if (array_is_created(&req.fields)) {
111 const struct oauth2_field *field;
112 bool got_sub = FALSE;
113 array_foreach(&req.fields, field) {
114 if (strcmp(field->name, "sub") == 0) {
115 test_assert_strcmp(field->value, "testuser");
116 got_sub = TRUE;
117 }
118 }
119 test_assert(got_sub == TRUE);
120 }
121
122 if (error != NULL)
123 i_error("%s", error);
124 }
125
create_jwt_token_kid(const char * algo,const char * kid)126 static buffer_t *create_jwt_token_kid(const char *algo, const char *kid)
127 {
128 /* make a token */
129 buffer_t *tokenbuf = t_buffer_create(64);
130
131 /* header */
132 base64url_encode_str(
133 t_strdup_printf(
134 "{\"alg\":\"%s\",\"typ\":\"JWT\",\"kid\":\"%s\"}",
135 algo, kid),
136 tokenbuf);
137 buffer_append(tokenbuf, ".", 1);
138
139 /* body */
140 base64url_encode_str(
141 t_strdup_printf("{\"sub\":\"testuser\","\
142 "\"iat\":%"PRIdTIME_T","
143 "\"exp\":%"PRIdTIME_T"}",
144 time(NULL), time(NULL)+600),
145 tokenbuf);
146 return tokenbuf;
147 }
148
create_jwt_token(const char * algo)149 static buffer_t *create_jwt_token(const char *algo)
150 {
151 /* make a token */
152 buffer_t *tokenbuf = t_buffer_create(64);
153
154 /* header */
155 base64url_encode_str(
156 t_strdup_printf("{\"alg\":\"%s\",\"typ\":\"JWT\"}", algo),
157 tokenbuf);
158 buffer_append(tokenbuf, ".", 1);
159
160 /* body */
161 base64url_encode_str(
162 t_strdup_printf("{\"sub\":\"testuser\","\
163 "\"iat\":%"PRIdTIME_T","
164 "\"exp\":%"PRIdTIME_T"}",
165 time(NULL), time(NULL)+600),
166 tokenbuf);
167 return tokenbuf;
168 }
169
170 static void
append_key_value(string_t * dest,const char * key,const char * value,bool str)171 append_key_value(string_t *dest, const char *key, const char *value, bool str)
172 {
173 str_append_c(dest, '"');
174 json_append_escaped(dest, key);
175 str_append(dest, "\":");
176 if (str)
177 str_append_c(dest, '"');
178 json_append_escaped(dest, value);
179 if (str)
180 str_append_c(dest, '"');
181
182 }
183
184 #define create_jwt_token_fields(algo, exp, iat, nbf, fields) \
185 create_jwt_token_fields_kid(algo, "default", exp, iat, nbf, fields)
186 static buffer_t *
create_jwt_token_fields_kid(const char * algo,const char * kid,time_t exp,time_t iat,time_t nbf,ARRAY_TYPE (oauth2_field)* fields)187 create_jwt_token_fields_kid(const char *algo, const char *kid, time_t exp, time_t iat,
188 time_t nbf, ARRAY_TYPE(oauth2_field) *fields)
189 {
190 const struct oauth2_field *field;
191 buffer_t *tokenbuf = t_buffer_create(64);
192 string_t *hdr = t_str_new(32);
193 str_printfa(hdr, "{\"alg\":\"%s\",\"typ\":\"JWT\"", algo);
194 if (kid != NULL && *kid != '\0') {
195 str_append(hdr, ",\"kid\":\"");
196 json_append_escaped(hdr, kid);
197 str_append_c(hdr, '"');
198 }
199 str_append(hdr, "}");
200 base64url_encode_str(str_c(hdr), tokenbuf);
201 buffer_append(tokenbuf, ".", 1);
202
203 string_t *bodybuf = t_str_new(64);
204 str_append_c(bodybuf, '{');
205 if (exp > 0) {
206 append_key_value(bodybuf, "exp", dec2str(exp), FALSE);
207 }
208 if (iat > 0) {
209 if (exp > 0)
210 str_append_c(bodybuf, ',');
211 append_key_value(bodybuf, "iat", dec2str(iat), FALSE);
212 }
213 if (nbf > 0) {
214 if (exp > 0 || iat > 0)
215 str_append_c(bodybuf, ',');
216 append_key_value(bodybuf, "nbf", dec2str(nbf), FALSE);
217 }
218 array_foreach(fields, field) {
219 if (str_data(bodybuf)[bodybuf->used-1] != '{')
220 str_append_c(bodybuf, ',');
221 append_key_value(bodybuf, field->name, field->value, TRUE);
222 }
223 str_append_c(bodybuf, '}');
224 base64url_encode_str(str_c(bodybuf), tokenbuf);
225
226 return tokenbuf;
227 }
228
229 #define save_key(algo, key) save_key_to(algo, "default", (key))
230 #define save_key_to(algo, name, key) save_key_azp_to(algo, "default", name, (key))
save_key_azp_to(const char * algo,const char * azp,const char * name,const char * keydata)231 static void save_key_azp_to(const char *algo, const char *azp,
232 const char *name, const char *keydata)
233 {
234 const char *error;
235 struct dict_op_settings set = {
236 .username = "testuser",
237 };
238 struct dict_transaction_context *ctx =
239 dict_transaction_begin(keys_dict, &set);
240 algo = t_str_ucase(algo);
241 dict_set(ctx, t_strconcat(DICT_PATH_SHARED, azp, "/", algo, "/",
242 name, NULL),
243 keydata);
244 if (dict_transaction_commit(&ctx, &error) < 0)
245 i_error("dict_set(%s) failed: %s", name, error);
246 }
247
sign_jwt_token_hs256(buffer_t * tokenbuf,buffer_t * key)248 static void sign_jwt_token_hs256(buffer_t *tokenbuf, buffer_t *key)
249 {
250 i_assert(key != NULL);
251 buffer_t *sig = t_hmac_buffer(&hash_method_sha256, key->data, key->used,
252 tokenbuf);
253 buffer_append(tokenbuf, ".", 1);
254 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
255 sig->data, sig->used, tokenbuf);
256 }
257
sign_jwt_token_hs384(buffer_t * tokenbuf,buffer_t * key)258 static void sign_jwt_token_hs384(buffer_t *tokenbuf, buffer_t *key)
259 {
260 i_assert(key != NULL);
261 buffer_t *sig = t_hmac_buffer(&hash_method_sha384, key->data, key->used,
262 tokenbuf);
263 buffer_append(tokenbuf, ".", 1);
264 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
265 sig->data, sig->used, tokenbuf);
266 }
267
sign_jwt_token_hs512(buffer_t * tokenbuf,buffer_t * key)268 static void sign_jwt_token_hs512(buffer_t *tokenbuf, buffer_t *key)
269 {
270 i_assert(key != NULL);
271 buffer_t *sig = t_hmac_buffer(&hash_method_sha512, key->data, key->used,
272 tokenbuf);
273 buffer_append(tokenbuf, ".", 1);
274 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
275 sig->data, sig->used, tokenbuf);
276 }
277
test_jwt_hs_token(void)278 static void test_jwt_hs_token(void)
279 {
280 test_begin("JWT HMAC token");
281
282 buffer_t *sign_key_384 = t_buffer_create(384/8);
283 void *ptr = buffer_append_space_unsafe(sign_key_384, 384/8);
284 random_fill(ptr, 384/8);
285 buffer_t *b64_key = t_base64_encode(0, SIZE_MAX,
286 sign_key_384->data,
287 sign_key_384->used);
288 save_key_to("HS384", "default", str_c(b64_key));
289 buffer_t *sign_key_512 = t_buffer_create(512/8);
290 ptr = buffer_append_space_unsafe(sign_key_512, 512/8);
291 random_fill(ptr, 512/8);
292 b64_key = t_base64_encode(0, SIZE_MAX,
293 sign_key_512->data,
294 sign_key_512->used);
295 save_key_to("HS512", "default", str_c(b64_key));
296 /* make a token */
297 buffer_t *tokenbuf = create_jwt_token("HS256");
298 /* sign it */
299 sign_jwt_token_hs256(tokenbuf, hs_sign_key);
300 test_jwt_token(str_c(tokenbuf));
301
302 tokenbuf = create_jwt_token("HS384");
303 sign_jwt_token_hs384(tokenbuf, sign_key_384);
304 test_jwt_token(str_c(tokenbuf));
305
306 tokenbuf = create_jwt_token("HS512");
307 sign_jwt_token_hs512(tokenbuf, sign_key_512);
308 test_jwt_token(str_c(tokenbuf));
309
310 test_end();
311 }
312
test_jwt_token_escape(void)313 static void test_jwt_token_escape(void)
314 {
315 struct test_case {
316 const char *azp;
317 const char *alg;
318 const char *kid;
319 const char *esc_azp;
320 const char *esc_kid;
321 } test_cases[] = {
322 { "", "hs256", "", "default", "default" },
323 { "", "hs256", "test", "default", "test" },
324 { "test", "hs256", "test", "test", "test" },
325 {
326 "http://test.unit/local%key",
327 "hs256",
328 "http://test.unit/local%key",
329 "http:%2f%2ftest.unit%2flocal%25key",
330 "http:%2f%2ftest.unit%2flocal%25key"
331 },
332 { "../", "hs256", "../", "..%2f", "..%2f" },
333 };
334
335 test_begin("JWT token escaping");
336
337 buffer_t *b64_key =
338 t_base64_encode(0, SIZE_MAX, hs_sign_key->data, hs_sign_key->used);
339 ARRAY_TYPE(oauth2_field) fields;
340 t_array_init(&fields, 8);
341
342 for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) {
343 const struct test_case *test_case = &test_cases[i];
344 array_clear(&fields);
345 struct oauth2_field *field = array_append_space(&fields);
346 field->name = "sub";
347 field->value = "testuser";
348 if (*test_case->azp != '\0') {
349 field = array_append_space(&fields);
350 field->name = "azp";
351 field->value = test_case->azp;
352 }
353 if (*test_case->kid != '\0') {
354 field = array_append_space(&fields);
355 field->name = "kid";
356 field->value = test_case->kid;
357 }
358 save_key_azp_to(test_case->alg, test_case->esc_azp, test_case->esc_kid,
359 str_c(b64_key));
360 buffer_t *token = create_jwt_token_fields_kid(test_case->alg,
361 test_case->kid,
362 time(NULL)+500,
363 time(NULL)-500,
364 0, &fields);
365 sign_jwt_token_hs256(token, hs_sign_key);
366 test_jwt_token(str_c(token));
367 }
368
369 test_end();
370 }
371
test_jwt_broken_token(void)372 static void test_jwt_broken_token(void)
373 {
374 struct test_cases {
375 const char *token;
376 bool is_jwt;
377 } test_cases[] = {
378 { /* empty token */
379 .token = "",
380 .is_jwt = FALSE
381 },
382 { /* not base64 */
383 .token = "{\"alg\":\"HS256\":\"typ\":\"JWT\"}",
384 .is_jwt = FALSE
385 },
386 { /* not jwt */
387 .token = "aGVsbG8sIHdvcmxkCg",
388 .is_jwt = FALSE
389 },
390 { /* no alg field */
391 .token = "eyJ0eXAiOiAiSldUIn0",
392 .is_jwt = FALSE
393 },
394 { /* no typ field */
395 .token = "eyJhbGciOiAiSFMyNTYifQ",
396 .is_jwt = FALSE
397 },
398 { /* typ field is wrong */
399 .token = "eyJ0eXAiOiAiand0IiwgImFsZyI6ICJIUzI1NiJ9."
400 "eyJhbGdvIjogIldURiIsICJ0eXAiOiAiSldUIn0."
401 "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo",
402 .is_jwt = FALSE
403 },
404 { /* unknown algorithm */
405 .token = "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJXVEYifQ."
406 "eyJhbGdvIjogIldURiIsICJ0eXAiOiAiSldUIn0."
407 "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo",
408 .is_jwt = TRUE
409 },
410 { /* truncated base64 */
411 .token = "yJhbGciOiJIUzI1NiIsInR5",
412 .is_jwt = FALSE
413 },
414 { /* missing body and signature */
415 .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
416 .is_jwt = FALSE
417 },
418 { /* empty body and signature */
419 .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..",
420 .is_jwt = TRUE
421 },
422 { /* empty signature */
423 .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
424 "eyJleHAiOjE1ODEzMzA3OTN9.",
425 .is_jwt = TRUE
426 },
427 { /* bad signature */
428 .token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
429 "eyJleHAiOjE1ODEzMzA3OTN9."
430 "q2wwwWWJVJxqw-J3uQ0DdlIyWfoZ7Z0QrdzvMW_B-jo",
431 .is_jwt = TRUE
432 },
433 };
434
435 test_begin("JWT broken tokens");
436
437 for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN {
438 struct test_cases *test_case = &test_cases[i];
439 struct oauth2_request req;
440 const char *error = NULL;
441 bool is_jwt;
442 test_assert_idx(parse_jwt_token(&req, test_case->token,
443 &is_jwt, &error) != 0, i);
444 test_assert_idx(test_case->is_jwt == is_jwt, i);
445 test_assert_idx(error != NULL, i);
446 } T_END;
447
448 test_end();
449 }
450
test_jwt_bad_valid_token(void)451 static void test_jwt_bad_valid_token(void)
452 {
453 test_begin("JWT bad token tests");
454 time_t now = time(NULL);
455
456 struct test_cases {
457 time_t exp;
458 time_t iat;
459 time_t nbf;
460 const char *key_values[20];
461 const char *error;
462 } test_cases[] =
463 {
464 { /* "empty" token */
465 .exp = 0,
466 .iat = 0,
467 .nbf = 0,
468 .key_values = { NULL },
469 .error = "Missing 'sub' field",
470 },
471 { /* missing sub field */
472 .exp = now+500,
473 .iat = 0,
474 .nbf = 0,
475 .key_values = { NULL },
476 .error = "Missing 'sub' field",
477 },
478 { /* no expiration */
479 .key_values = {
480 "sub", "testuser",
481 NULL
482 },
483 .error = "Missing 'exp' field",
484 },
485 { /* non-ISO date as iat */
486 .exp = now+500,
487 .iat = 0,
488 .nbf = 0,
489 .key_values = { "sub", "testuser", "iat",
490 "1.1.2019 16:00", NULL },
491 .error = "Malformed 'iat' field"
492 },
493 { /* expired token */
494 .exp = now-500,
495 .iat = 0,
496 .nbf = 0,
497 .key_values = { "sub", "testuser", NULL },
498 .error = "Token has expired",
499 },
500 { /* future token */
501 .exp = now+1000,
502 .iat = now+500,
503 .nbf = 0,
504 .key_values = { "sub", "testuser", NULL },
505 .error = "Token is issued in future",
506 },
507 { /* token not valid yet */
508 .exp = now+500,
509 .iat = now,
510 .nbf = now+250,
511 .key_values = { "sub", "testuser", NULL },
512 .error = "Token is not valid yet",
513 },
514 };
515
516 for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN {
517 const struct test_cases *test_case = &test_cases[i];
518 const char *key = NULL;
519 ARRAY_TYPE(oauth2_field) fields;
520
521 t_array_init(&fields, 8);
522 for (const char *const *value = test_case->key_values;
523 *value != NULL; value++) {
524 if (key == NULL) {
525 key = *value;
526 } else {
527 struct oauth2_field *field =
528 array_append_space(&fields);
529 field->name = key;
530 field->value = *value;
531 key = NULL;
532 }
533 }
534
535 buffer_t *tokenbuf =
536 create_jwt_token_fields("HS256", test_case->exp,
537 test_case->iat, test_case->nbf,
538 &fields);
539 sign_jwt_token_hs256(tokenbuf, hs_sign_key);
540
541 struct oauth2_request req;
542 const char *error = NULL;
543 bool is_jwt;
544
545 test_assert_idx(parse_jwt_token(&req, str_c(tokenbuf),
546 &is_jwt, &error) != 0, i);
547 test_assert_idx(is_jwt == TRUE, i);
548 if (test_case->error != NULL) {
549 test_assert_strcmp(test_case->error, error);
550 }
551 test_assert(error != NULL);
552 } T_END;
553
554 test_end();
555 }
556
test_jwt_valid_token(void)557 static void test_jwt_valid_token(void)
558 {
559 test_begin("JWT valid token tests");
560 time_t now = time(NULL);
561
562 struct test_cases {
563 time_t exp;
564 time_t iat;
565 time_t nbf;
566 const char *key_values[20];
567 } test_cases[] = {
568 { /* valid token */
569 .exp = now + 500,
570 .key_values = {
571 "sub", "testuser",
572 NULL
573 },
574 },
575 {
576 .exp = now + 500,
577 .nbf = now - 500,
578 .iat = now - 250,
579 .key_values = {
580 "sub", "testuser",
581 NULL
582 },
583 },
584 { /* token issued in advance */
585 .exp = now + 500,
586 .nbf = now - 500,
587 .iat = now - 3600,
588 .key_values = {
589 "sub", "testuser",
590 NULL,
591 },
592 },
593 };
594
595 for (size_t i = 0; i < N_ELEMENTS(test_cases); i++) T_BEGIN {
596 const struct test_cases *test_case = &test_cases[i];
597 ARRAY_TYPE(oauth2_field) fields;
598
599 t_array_init(&fields, 8);
600 for (unsigned int i = 0; test_case->key_values[i] != NULL; i += 2) {
601 struct oauth2_field *field = array_append_space(&fields);
602 field->name = test_case->key_values[i];
603 field->value = test_case->key_values[i+1];
604 }
605
606 buffer_t *tokenbuf =
607 create_jwt_token_fields("HS256", test_case->exp,
608 test_case->iat, test_case->nbf,
609 &fields);
610 sign_jwt_token_hs256(tokenbuf, hs_sign_key);
611
612 struct oauth2_request req;
613 const char *error = NULL;
614 bool is_jwt;
615
616 test_assert_idx(parse_jwt_token(&req, str_c(tokenbuf),
617 &is_jwt, &error) == 0, i);
618 test_assert_idx(is_jwt == TRUE, i);
619 test_assert_idx(error == NULL, i);
620 if (error != NULL)
621 i_error("JWT validation error: %s", error);
622 } T_END;
623
624 test_end();
625 }
626
test_jwt_dates(void)627 static void test_jwt_dates(void)
628 {
629 test_begin("JWT Token dates");
630
631 /* simple check to make sure ISO8601 dates work too */
632 ARRAY_TYPE(oauth2_field) fields;
633 t_array_init(&fields, 8);
634 struct oauth2_field *field;
635 struct tm tm_b;
636 struct tm *tm;
637 time_t now = time(NULL);
638 time_t exp = now+500;
639 time_t nbf = now-250;
640 time_t iat = now-500;
641
642 field = array_append_space(&fields);
643 field->name = "sub";
644 field->value = "testuser";
645 field = array_append_space(&fields);
646 field->name = "exp";
647 tm = gmtime_r(&exp, &tm_b);
648 field->value = iso8601_date_create_tm(tm, INT_MAX);
649 field = array_append_space(&fields);
650 field->name = "nbf";
651 tm = gmtime_r(&nbf, &tm_b);
652 field->value = iso8601_date_create_tm(tm, INT_MAX);
653 field = array_append_space(&fields);
654 field->name = "iat";
655 tm = gmtime_r(&iat, &tm_b);
656 field->value = iso8601_date_create_tm(tm, INT_MAX);
657 buffer_t *tokenbuf = create_jwt_token_fields("HS256", 0, 0, 0, &fields);
658 sign_jwt_token_hs256(tokenbuf, hs_sign_key);
659 test_jwt_token(str_c(tokenbuf));
660
661 str_truncate(tokenbuf, 0);
662 base64url_encode_str("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", tokenbuf);
663 str_append_c(tokenbuf, '.');
664 base64url_encode_str(t_strdup_printf("{\"sub\":\"testuser\","
665 "\"exp\":%"PRIdTIME_T","
666 "\"nbf\":0,\"iat\":%"PRIdTIME_T"}",
667 exp, iat),
668 tokenbuf);
669 sign_jwt_token_hs256(tokenbuf, hs_sign_key);
670 test_jwt_token(str_c(tokenbuf));
671
672 test_end();
673 }
674
test_jwt_key_files(void)675 static void test_jwt_key_files(void)
676 {
677 test_begin("JWT key id");
678 /* write HMAC secrets */
679 struct oauth2_request req;
680 bool is_jwt;
681 const char *error = NULL;
682
683 buffer_t *secret = t_buffer_create(32);
684 void *ptr = buffer_append_space_unsafe(secret, 32);
685 random_fill(ptr, 32);
686 buffer_t *b64_key = t_base64_encode(0, SIZE_MAX,
687 secret->data, secret->used);
688 save_key_to("HS256", "first", str_c(b64_key));
689 buffer_t *secret2 = t_buffer_create(32);
690 ptr = buffer_append_space_unsafe(secret2, 32);
691 random_fill(ptr, 32);
692 b64_key = t_base64_encode(0, SIZE_MAX, secret2->data, secret2->used);
693 save_key_to("HS256", "second", str_c(b64_key));
694
695 /* create and sign token */
696 buffer_t *token_1 = create_jwt_token_kid("HS256", "first");
697 buffer_t *token_2 = create_jwt_token_kid("HS256", "second");
698 buffer_t *token_3 = create_jwt_token_kid("HS256", "missing");
699 buffer_t *token_4 = create_jwt_token_kid("HS256", "");
700
701 sign_jwt_token_hs256(token_1, secret);
702 sign_jwt_token_hs256(token_2, secret2);
703 sign_jwt_token_hs256(token_3, secret);
704 sign_jwt_token_hs256(token_4, secret);
705
706 test_jwt_token(str_c(token_1));
707 test_jwt_token(str_c(token_2));
708
709 test_assert(parse_jwt_token(&req, str_c(token_3), &is_jwt, &error) != 0);
710 test_assert(is_jwt == TRUE);
711 test_assert_strcmp(error, "HS256 key 'missing' not found");
712 test_assert(parse_jwt_token(&req, str_c(token_4), &is_jwt, &error) != 0);
713 test_assert(is_jwt == TRUE);
714 test_assert_strcmp(error, "'kid' field is empty");
715
716 test_end();
717 }
718
test_jwt_kid_escape(void)719 static void test_jwt_kid_escape(void)
720 {
721 test_begin("JWT kid escape");
722 /* save a token */
723 buffer_t *secret = t_buffer_create(32);
724 void *ptr = buffer_append_space_unsafe(secret, 32);
725 random_fill(ptr, 32);
726 buffer_t *b64_key = t_base64_encode(0, SIZE_MAX,
727 secret->data, secret->used);
728 save_key_to("HS256", "hello.world%2f%25", str_c(b64_key));
729 /* make a token */
730 buffer_t *tokenbuf = create_jwt_token_kid("HS256", "hello.world/%");
731 /* sign it */
732 sign_jwt_token_hs256(tokenbuf, secret);
733 test_jwt_token(str_c(tokenbuf));
734 test_end();
735 }
736
test_jwt_rs_token(void)737 static void test_jwt_rs_token(void)
738 {
739 const char *error;
740
741 if (skip_dcrypt)
742 return;
743
744 test_begin("JWT RSA token");
745 /* write public key to file */
746 oauth2_validation_key_cache_evict(key_cache, "default");
747 save_key("RS256", rsa_public_key);
748
749 buffer_t *tokenbuf = create_jwt_token("RS256");
750
751 /* sign token */
752 buffer_t *sig = t_buffer_create(64);
753 struct dcrypt_private_key *key;
754 if (!dcrypt_key_load_private(&key, rsa_private_key, NULL, NULL,
755 &error) ||
756 !dcrypt_sign(key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS,
757 tokenbuf->data, tokenbuf->used, sig,
758 DCRYPT_PADDING_RSA_PKCS1, &error)) {
759 i_error("dcrypt signing failed: %s", error);
760 lib_exit(1);
761 }
762 dcrypt_key_unref_private(&key);
763
764 /* convert to base64 */
765 buffer_append(tokenbuf, ".", 1);
766 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
767 sig->data, sig->used, tokenbuf);
768
769 test_jwt_token(str_c(tokenbuf));
770
771 test_end();
772 }
773
test_jwt_ps_token(void)774 static void test_jwt_ps_token(void)
775 {
776 const char *error;
777
778 if (skip_dcrypt)
779 return;
780
781 test_begin("JWT RSAPSS token");
782 /* write public key to file */
783 oauth2_validation_key_cache_evict(key_cache, "default");
784 save_key("PS256", rsa_public_key);
785
786 buffer_t *tokenbuf = create_jwt_token("PS256");
787
788 /* sign token */
789 buffer_t *sig = t_buffer_create(64);
790 struct dcrypt_private_key *key;
791 if (!dcrypt_key_load_private(&key, rsa_private_key, NULL, NULL,
792 &error) ||
793 !dcrypt_sign(key, "sha256", DCRYPT_SIGNATURE_FORMAT_DSS,
794 tokenbuf->data, tokenbuf->used, sig,
795 DCRYPT_PADDING_RSA_PKCS1_PSS, &error)) {
796 i_error("dcrypt signing failed: %s", error);
797 lib_exit(1);
798 }
799 dcrypt_key_unref_private(&key);
800
801 /* convert to base64 */
802 buffer_append(tokenbuf, ".", 1);
803 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
804 sig->data, sig->used, tokenbuf);
805
806 test_jwt_token(str_c(tokenbuf));
807
808 test_end();
809 }
810
test_jwt_ec_token(void)811 static void test_jwt_ec_token(void)
812 {
813 const char *error;
814
815 if (skip_dcrypt)
816 return;
817
818 test_begin("JWT ECDSA token");
819 struct dcrypt_keypair pair;
820 i_zero(&pair);
821 if (!dcrypt_keypair_generate(&pair, DCRYPT_KEY_EC, 0,
822 "prime256v1", &error)) {
823 i_error("dcrypt keypair generate failed: %s", error);
824 lib_exit(1);
825 }
826 /* export public key */
827 buffer_t *keybuf = t_buffer_create(256);
828 if (!dcrypt_key_store_public(pair.pub, DCRYPT_FORMAT_PEM, keybuf,
829 &error)) {
830 i_error("dcrypt key store failed: %s", error);
831 lib_exit(1);
832 }
833 oauth2_validation_key_cache_evict(key_cache, "default");
834 save_key("ES256", str_c(keybuf));
835
836 buffer_t *tokenbuf = create_jwt_token("ES256");
837
838 /* sign token */
839 buffer_t *sig = t_buffer_create(64);
840 if (!dcrypt_sign(pair.priv, "sha256", DCRYPT_SIGNATURE_FORMAT_X962,
841 tokenbuf->data, tokenbuf->used, sig,
842 DCRYPT_PADDING_DEFAULT, &error)) {
843 i_error("dcrypt signing failed: %s", error);
844 lib_exit(1);
845 }
846 dcrypt_keypair_unref(&pair);
847
848 /* convert to base64 */
849 buffer_append(tokenbuf, ".", 1);
850 base64url_encode(BASE64_ENCODE_FLAG_NO_PADDING, SIZE_MAX,
851 sig->data, sig->used, tokenbuf);
852 test_jwt_token(str_c(tokenbuf));
853
854 test_end();
855 }
856
test_do_init(void)857 static void test_do_init(void)
858 {
859 const char *error;
860 struct dcrypt_settings dcrypt_set = {
861 .module_dir = "../lib-dcrypt/.libs",
862 };
863 struct dict_settings dict_set = {
864 .base_dir = ".",
865 };
866
867 i_unlink_if_exists(".keys");
868 dict_driver_register(&dict_driver_file);
869 if (dict_init("file:.keys", &dict_set, &keys_dict, &error) < 0)
870 i_fatal("dict_init(file:.keys): %s", error);
871 if (!dcrypt_initialize(NULL, &dcrypt_set, &error)) {
872 i_error("No functional dcrypt backend found - "
873 "skipping some tests: %s", error);
874 skip_dcrypt = TRUE;
875 }
876 key_cache = oauth2_validation_key_cache_init();
877
878 /* write HMAC secret */
879 hs_sign_key =buffer_create_dynamic(default_pool, 32);
880 void *ptr = buffer_append_space_unsafe(hs_sign_key, 32);
881 random_fill(ptr, 32);
882 buffer_t *b64_key = t_base64_encode(0, SIZE_MAX,
883 hs_sign_key->data,
884 hs_sign_key->used);
885 save_key("HS256", str_c(b64_key));
886 }
887
test_do_deinit(void)888 static void test_do_deinit(void)
889 {
890 dict_deinit(&keys_dict);
891 dict_driver_unregister(&dict_driver_file);
892 oauth2_validation_key_cache_deinit(&key_cache);
893 i_unlink(".keys");
894 buffer_free(&hs_sign_key);
895 dcrypt_deinitialize();
896 }
897
main(void)898 int main(void)
899 {
900 static void (*test_functions[])(void) = {
901 test_do_init,
902 test_jwt_hs_token,
903 test_jwt_token_escape,
904 test_jwt_valid_token,
905 test_jwt_bad_valid_token,
906 test_jwt_broken_token,
907 test_jwt_dates,
908 test_jwt_key_files,
909 test_jwt_kid_escape,
910 test_jwt_rs_token,
911 test_jwt_ps_token,
912 test_jwt_ec_token,
913 test_do_deinit,
914 NULL
915 };
916 int ret;
917 ret = test_run(test_functions);
918 return ret;
919 }
920