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