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