1/*
2 * Sigv4 support for Secure Streams
3 *
4 * libwebsockets - small server side websockets and web server implementation
5 *
6 * Copyright (C) 2020 Andy Green <andy@warmcat.com>
7 *                    securestreams-dev@amazon.com
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to
11 * deal in the Software without restriction, including without limitation the
12 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
13 * sell copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
25 * IN THE SOFTWARE.
26 */
27
28#include <private-lib-core.h>
29
30struct sigv4_header {
31	const char * name;
32	const char * value;
33};
34
35#define MAX_HEADER_NUM 8
36struct sigv4 {
37	struct sigv4_header headers[MAX_HEADER_NUM];
38	uint8_t	hnum;
39	char	ymd[10];     /*YYYYMMDD*/
40	const char *timestamp;
41	const char *payload_hash;
42	const char *region;
43	const char *service;
44};
45
46static const uint8_t blob_idx[] = {
47	LWS_SYSBLOB_TYPE_EXT_AUTH1,
48	LWS_SYSBLOB_TYPE_EXT_AUTH2,
49	LWS_SYSBLOB_TYPE_EXT_AUTH3,
50	LWS_SYSBLOB_TYPE_EXT_AUTH4,
51};
52
53enum {
54	LWS_SS_SIGV4_KEYID,
55	LWS_SS_SIGV4_KEY,
56	LWS_SS_SIGV4_BLOB_SLOTS
57};
58
59static inline int add_header(struct sigv4 *s, const char *name, const char *value)
60{
61	if (s->hnum >= MAX_HEADER_NUM) {
62		lwsl_err("%s too many sigv4 headers\n", __func__);
63		return -1;
64	}
65
66	s->headers[s->hnum].name = name;
67	s->headers[s->hnum].value = value;
68	s->hnum++;
69
70	if (!strncmp(name, "x-amz-content-sha256", strlen("x-amz-content-sha256")))
71		s->payload_hash = value;
72
73	if (!strncmp(name, "x-amz-date", strlen("x-amz-date"))) {
74		s->timestamp = value;
75		strncpy(s->ymd, value, 8);
76	}
77
78	return 0;
79}
80
81static int
82cmp_header(const void * a, const void * b)
83{
84	return strcmp(((struct sigv4_header *)a)->name,
85			((struct sigv4_header *)b)->name);
86}
87
88static int
89init_sigv4(struct lws *wsi, struct lws_ss_handle *h, struct sigv4 *s)
90{
91	lws_ss_metadata_t *polmd = h->policy->metadata;
92	int m = 0;
93
94	add_header(s, "host:", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST));
95
96	while (polmd) {
97		if (polmd->value__may_own_heap &&
98		    ((uint8_t *)polmd->value__may_own_heap)[0] &&
99		    h->metadata[m].value__may_own_heap) {
100			/* consider all headers start with "x-amz-" need to be signed */
101			if (!strncmp(polmd->value__may_own_heap, "x-amz-",
102				     strlen("x-amz-"))) {
103				if (add_header(s, polmd->value__may_own_heap,
104					       h->metadata[m].value__may_own_heap))
105					return -1;
106			}
107		}
108		if (!strcmp(h->metadata[m].name, h->policy->aws_region) &&
109		    h->metadata[m].value__may_own_heap)
110			s->region = h->metadata[m].value__may_own_heap;
111
112		if (!strcmp(h->metadata[m].name, h->policy->aws_service) &&
113		    h->metadata[m].value__may_own_heap)
114			s->service = h->metadata[m].value__may_own_heap;
115
116		m++;
117		polmd = polmd->next;
118	}
119
120	qsort(s->headers, s->hnum, sizeof(struct sigv4_header), cmp_header);
121
122#if 0
123	do {
124		int i;
125		for (i= 0; i<s->hnum; i++)
126			lwsl_debug("%s hdr %s %s\n", __func__,
127					s->headers[i].name, s->headers[i].value);
128
129		lwsl_debug("%s service: %s region: %s\n", __func__,
130				s->service, s->region);
131	} while(0);
132#endif
133
134	return 0;
135}
136
137static void
138bin2hex(uint8_t *in, size_t len, char *out)
139{
140	static const char *hex = "0123456789abcdef";
141	size_t n;
142
143	for (n = 0; n < len; n++) {
144		*out++ = hex[(in[n] >> 4) & 0xf];
145		*out++ = hex[in[n] & 15];
146	}
147	*out = '\0';
148}
149
150static int
151hmacsha256(const uint8_t *key, size_t keylen, const uint8_t *txt,
152			size_t txtlen, uint8_t *digest)
153{
154	struct lws_genhmac_ctx hmacctx;
155
156	if (lws_genhmac_init(&hmacctx, LWS_GENHMAC_TYPE_SHA256,
157				key, keylen))
158		return -1;
159
160	if (lws_genhmac_update(&hmacctx, txt, txtlen)) {
161		lwsl_err("%s: hmac computation failed\n", __func__);
162		lws_genhmac_destroy(&hmacctx, NULL);
163		return -1;
164	}
165
166	if (lws_genhmac_destroy(&hmacctx, digest)) {
167		lwsl_err("%s: problem destroying hmac\n", __func__);
168		return -1;
169	}
170
171	return 0;
172}
173
174/* cut the last byte of the str */
175static inline int hash_update_bite_str(struct lws_genhash_ctx *ctx, const char * str)
176{
177	int ret = 0;
178	if ((ret = lws_genhash_update(ctx, (void *)str, strlen(str)-1))) {
179		lws_genhash_destroy(ctx, NULL);
180		lwsl_err("%s err %d line \n", __func__, ret);
181	}
182	return ret;
183}
184
185static inline int hash_update_str(struct lws_genhash_ctx *ctx, const char * str)
186{
187	int ret = 0;
188	if ((ret = lws_genhash_update(ctx, (void *)str, strlen(str)))) {
189		lws_genhash_destroy(ctx, NULL);
190		lwsl_err("%s err %d \n", __func__, ret);
191	}
192	return ret;
193}
194
195static int
196build_sign_string(struct lws *wsi, char *buf, size_t bufsz,
197		struct lws_ss_handle *h, struct sigv4 *s)
198{
199	char hash[65], *end = &buf[bufsz - 1], *start;
200	struct lws_genhash_ctx hash_ctx;
201	uint8_t hash_bin[32];
202	int i, ret = 0;
203
204	start = buf;
205
206	if ((ret = lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))) {
207		lws_genhash_destroy(&hash_ctx, NULL);
208		lwsl_err("%s genhash init err %d \n", __func__, ret);
209		return -1;
210	}
211	/*
212	 * hash canonical_request
213	 */
214
215	if (hash_update_str(&hash_ctx, h->policy->u.http.method) ||
216			hash_update_str(&hash_ctx, "\n"))
217		return -1;
218	if (hash_update_str(&hash_ctx, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)) ||
219			hash_update_str(&hash_ctx, "\n"))
220		return -1;
221
222	/* TODO, append query string */
223	if (hash_update_str(&hash_ctx, "\n"))
224		return -1;
225
226	for (i = 0; i < s->hnum; i++) {
227		if (hash_update_str(&hash_ctx, s->headers[i].name) ||
228		    hash_update_str(&hash_ctx, s->headers[i].value) ||
229		    hash_update_str(&hash_ctx, "\n"))
230		return -1;
231
232	}
233	if (hash_update_str(&hash_ctx, "\n"))
234		return -1;
235
236	for (i = 0; i < s->hnum-1; i++) {
237		if (hash_update_bite_str(&hash_ctx, s->headers[i].name) ||
238		    hash_update_str(&hash_ctx, ";"))
239			return -1;
240	}
241	if (hash_update_bite_str(&hash_ctx, s->headers[i].name) ||
242	    hash_update_str(&hash_ctx, "\n") ||
243	    hash_update_str(&hash_ctx, s->payload_hash))
244		return -1;
245
246	if ((ret = lws_genhash_destroy(&hash_ctx, hash_bin))) {
247		lws_genhash_destroy(&hash_ctx, NULL);
248		lwsl_err("%s lws_genhash error \n", __func__);
249		return -1;
250	}
251
252	bin2hex(hash_bin, sizeof(hash_bin), hash);
253	/*
254	 * build sign string like the following
255	 *
256	 * "AWS4-HMAC-SHA256" + "\n" +
257	 * timeStampISO8601Format + "\n" +
258	 * date.Format(<YYYYMMDD>) + "/" + <region> + "/" + <service> + "/aws4_request" + "\n" +
259	 * Hex(SHA256Hash(<CanonicalRequest>))
260	 */
261	buf = start;
262
263	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n",
264							"AWS4-HMAC-SHA256");
265	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n",
266							s->timestamp);
267	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s/%s/%s/%s\n",
268				s->ymd, s->region, s->service, "aws4_request");
269
270	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", hash);
271	*buf++ = '\0';
272
273	assert(buf <= start + bufsz);
274
275	return 0;
276}
277
278/*
279 * DateKey              = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>")
280 * DateRegionKey        = HMAC-SHA256(<DateKey>, "<aws-region>")
281 * DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>")
282 * SigningKey           = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
283 */
284static int
285calc_signing_key(struct lws *wsi, struct lws_ss_handle *h,
286		struct sigv4 *s, uint8_t *sign_key)
287{
288	uint8_t key[128], date_key[32], and_region_key[32],
289		and_service_key[32], *kb;
290	lws_system_blob_t *ab;
291	size_t keylen;
292	int n;
293
294	ab = lws_system_get_blob(wsi->a.context,
295				 blob_idx[h->policy->auth->blob_index],
296				 LWS_SS_SIGV4_KEY);
297	if (!ab)
298		return -1;
299
300	kb = key;
301
302	*kb++ = 'A';
303	*kb++ = 'W';
304	*kb++ = 'S';
305	*kb++ = '4';
306
307	keylen = sizeof(key) - 4;
308	if (lws_system_blob_get_size(ab) > keylen - 1)
309		return -1;
310
311	n = lws_system_blob_get(ab, kb, &keylen, 0);
312	if (n < 0)
313		return -1;
314
315	kb[keylen] = '\0';
316
317	hmacsha256((const uint8_t *)key, strlen((const char *)key),
318		   (const uint8_t *)s->ymd, strlen(s->ymd), date_key);
319
320	hmacsha256(date_key, sizeof(date_key), (const uint8_t *)s->region,
321		   strlen(s->region), and_region_key);
322
323	hmacsha256(and_region_key, sizeof(and_region_key),
324		   (const uint8_t *)s->service,
325		   strlen(s->service), and_service_key);
326
327	hmacsha256(and_service_key, sizeof(and_service_key),
328		   (uint8_t *)"aws4_request",
329		   strlen("aws4_request"), sign_key);
330
331	return 0;
332}
333
334/* Sample auth string:
335 *
336 * 'Authorization: AWS4-HMAC-SHA256 Credential=AKIAVHWASOFE7TJ7ZUQY/20200731/us-west-2/s3/aws4_request,
337* SignedHeaders=host;x-amz-content-sha256;x-amz-date, \
338* Signature=ad9fb75ff3b46c7990e3e8f090abfdd6c01fd67761a517111694377e20698377'
339*/
340static int
341build_auth_string(struct lws *wsi, char * buf, size_t bufsz,
342		struct lws_ss_handle *h, struct sigv4 *s,
343		uint8_t *signature_bin)
344{
345	char *start = buf, *end = &buf[bufsz - 1];
346	char *c;
347	lws_system_blob_t *ab;
348	size_t keyidlen = 128; // max keyid len is 128
349	int n;
350
351	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
352			    "AWS4-HMAC-SHA256 ");
353
354	ab = lws_system_get_blob(wsi->a.context,
355				 blob_idx[h->policy->auth->blob_index],
356				 LWS_SS_SIGV4_KEYID);
357	if (!ab)
358		return -1;
359
360	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
361							"Credential=");
362	n = lws_system_blob_get(ab,(uint8_t *)buf, &keyidlen, 0);
363	if (n < 0)
364		return -1;
365	buf += keyidlen;
366
367	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "/%s/%s/%s/%s, ",
368				s->ymd, s->region, s->service, "aws4_request");
369	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
370							"SignedHeaders=");
371	for (n = 0; n < s->hnum; n++) {
372		buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
373					"%s",s->headers[n].name);
374		buf--; /* remove ':' */
375		*buf++ = ';';
376	}
377	c = buf - 1;
378	*c = ','; /* overwrite ';' back to ',' */
379
380	buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
381			    "%s", " Signature=");
382	bin2hex(signature_bin, 32, buf);
383
384	assert(buf+65 <= start + bufsz);
385
386	lwsl_debug("%s %s\n", __func__, start);
387
388	return 0;
389
390}
391
392int
393lws_ss_apply_sigv4(struct lws *wsi, struct lws_ss_handle *h,
394		     unsigned char **p, unsigned char *end)
395{
396	uint8_t buf[512], sign_key[32], signature_bin[32], *bp;
397	struct sigv4 s;
398
399	memset(&s, 0, sizeof(s));
400
401	bp = buf;
402
403	init_sigv4(wsi, h, &s);
404	if (!s.timestamp || !s.payload_hash) {
405		lwsl_err("%s missing headers\n", __func__);
406		return -1;
407	}
408
409	if (build_sign_string(wsi, (char *)bp, sizeof(buf), h, &s))
410		return -1;
411
412	if (calc_signing_key(wsi, h, &s, sign_key))
413		return -1;
414
415	hmacsha256(sign_key, sizeof(sign_key), (const uint8_t *)buf,
416			      strlen((const char *)buf), signature_bin);
417
418	bp = buf; /* reuse for auth_str */
419	if (build_auth_string(wsi, (char *)bp, sizeof(buf), h, &s,
420				signature_bin))
421		return -1;
422
423	if (lws_add_http_header_by_name(wsi,
424					(const uint8_t *)"Authorization:", buf,
425					(int)strlen((const char*)buf), p, end))
426		return -1;
427
428	return 0;
429}
430
431int
432lws_ss_sigv4_set_aws_key(struct lws_context* context, uint8_t idx,
433		                const char * keyid, const char * key)
434{
435	const char * s[] = { keyid, key };
436	lws_system_blob_t *ab;
437	int i;
438
439	if (idx > LWS_ARRAY_SIZE(blob_idx))
440		return -1;
441
442	for (i = 0; i < LWS_SS_SIGV4_BLOB_SLOTS; i++) {
443		ab = lws_system_get_blob(context, blob_idx[idx], i);
444		if (!ab)
445			return -1;
446
447		lws_system_blob_heap_empty(ab);
448
449		if (lws_system_blob_heap_append(ab, (const uint8_t *)s[i],
450						strlen(s[i]))) {
451			lwsl_err("%s: can't store %d \n", __func__, i);
452
453			return -1;
454		}
455	}
456
457	return 0;
458}
459
460#if defined(__linux__) || defined(__APPLE__) || defined(WIN32) || \
461	defined(__FreeBSD__) || defined(__NetBSD__) || defined(__ANDROID__) || \
462	defined(__sun) || defined(__OpenBSD__)
463
464/* ie, if we have filesystem ops */
465
466int
467lws_aws_filesystem_credentials_helper(const char *path, const char *kid,
468				      const char *ak, char **aws_keyid,
469				      char **aws_key)
470{
471	char *str = NULL, *val = NULL, *line = NULL, sth[128];
472	size_t len = sizeof(sth);
473	const char *home = "";
474	int i, poff = 0;
475	ssize_t rd;
476	FILE *fp;
477
478	*aws_keyid = *aws_key = NULL;
479
480	if (path[0] == '~') {
481		home = getenv("HOME");
482		if (home && strlen(home) > sizeof(sth) - 1) /* coverity */
483			return -1;
484		else {
485			if (!home)
486				home = "";
487
488			poff = 1;
489		}
490	}
491	lws_snprintf(sth, sizeof(sth), "%s%s", home, path + poff);
492
493	fp = fopen(sth, "r");
494	if (!fp) {
495		lwsl_err("%s can't open '%s'\n", __func__, sth);
496
497		return -1;
498	}
499
500	while ((rd = getline(&line, &len, fp)) != -1) {
501		for (i = 0; i < 2; i++) {
502			size_t slen;
503
504			if (strncmp(line, i ? kid : ak, strlen(i ? kid : ak)))
505				continue;
506
507			str = strchr(line, '=');
508			if (!str)
509				continue;
510
511			str++;
512
513			/* only read the first key for each */
514			if (*(i ? aws_keyid : aws_key))
515				continue;
516
517			/*
518			 * Trim whitespace from the start and end
519			 */
520
521			slen = (size_t)(rd - lws_ptr_diff(str, line));
522
523			while (slen && *str == ' ') {
524				str++;
525				slen--;
526			}
527
528			while (slen && (str[slen - 1] == '\r' ||
529					str[slen - 1] == '\n' ||
530					str[slen - 1] == ' '))
531				slen--;
532
533			val = malloc(slen + 1);
534			if (!val)
535				goto bail;
536
537			strncpy(val, str, slen);
538			val[slen] = '\0';
539
540			*(i ? aws_keyid : aws_key) = val;
541
542		}
543	}
544
545bail:
546	fclose(fp);
547
548	if (line)
549		free(line);
550
551	if (!*aws_keyid || !*aws_key) {
552		if (*aws_keyid) {
553			free(*aws_keyid);
554			*aws_keyid = NULL;
555		}
556		if (*aws_key) {
557			free(*aws_key);
558			*aws_key = NULL;
559		}
560		lwsl_err("%s can't find aws credentials! \
561				please check %s\n", __func__, path);
562		return -1;
563	}
564
565	lwsl_info("%s: '%s' '%s'\n", __func__, *aws_keyid, *aws_key);
566
567	return 0;
568}
569#endif
570