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