• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
30 struct sigv4_header {
31 	const char * name;
32 	const char * value;
33 };
34 
35 #define MAX_HEADER_NUM 8
36 struct 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 
46 static 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 
53 enum {
54 	LWS_SS_SIGV4_KEYID,
55 	LWS_SS_SIGV4_KEY,
56 	LWS_SS_SIGV4_BLOB_SLOTS
57 };
58 
add_header(struct sigv4 * s,const char * name,const char * value)59 static 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 
81 static int
cmp_header(const void * a,const void * b)82 cmp_header(const void * a, const void * b)
83 {
84 	return strcmp(((struct sigv4_header *)a)->name,
85 			((struct sigv4_header *)b)->name);
86 }
87 
88 static int
init_sigv4(struct lws * wsi,struct lws_ss_handle * h,struct sigv4 * s)89 init_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 
137 static void
bin2hex(uint8_t * in,size_t len,char * out)138 bin2hex(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 
150 static int
hmacsha256(const uint8_t * key,size_t keylen,const uint8_t * txt,size_t txtlen,uint8_t * digest)151 hmacsha256(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 */
hash_update_bite_str(struct lws_genhash_ctx * ctx,const char * str)175 static 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 
hash_update_str(struct lws_genhash_ctx * ctx,const char * str)185 static 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 
195 static int
build_sign_string(struct lws * wsi,char * buf,size_t bufsz,struct lws_ss_handle * h,struct sigv4 * s)196 build_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  */
284 static int
calc_signing_key(struct lws * wsi,struct lws_ss_handle * h,struct sigv4 * s,uint8_t * sign_key)285 calc_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 */
340 static int
build_auth_string(struct lws * wsi,char * buf,size_t bufsz,struct lws_ss_handle * h,struct sigv4 * s,uint8_t * signature_bin)341 build_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 
392 int
lws_ss_apply_sigv4(struct lws * wsi,struct lws_ss_handle * h,unsigned char ** p,unsigned char * end)393 lws_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 
431 int
lws_ss_sigv4_set_aws_key(struct lws_context * context,uint8_t idx,const char * keyid,const char * key)432 lws_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 
466 int
lws_aws_filesystem_credentials_helper(const char * path,const char * kid,const char * ak,char ** aws_keyid,char ** aws_key)467 lws_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 
545 bail:
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