diff --git a/.sai.json b/.sai.json index 6a89b9cb4..76da2b229 100644 --- a/.sai.json +++ b/.sai.json @@ -146,7 +146,7 @@ "platforms": "windows-10/x86_64-amd/msvc, windows-10/x86_64-amd/noptmsvc" }, "secure-streams-proxy": { - "cmake": "-DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_SECURE_STREAMS_PROXY_API=1 -DLWS_WITH_MINIMAL_EXAMPLES=1", + "cmake": "-DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_SECURE_STREAMS_PROXY_API=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_SECURE_STREAMS_AUTH_SIGV4=1", "platforms": "not windows-10/x86_64-amd/msvc, netbsd/aarch64BE-bcm2837-a53/gcc" }, "distro_recommended": { # minimal examples also needed for ctest diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index a67c9938a..eb4f306b5 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -169,6 +169,10 @@ if (LWS_WITH_HTTP_BASIC_AUTH) set(LWS_WITH_HTTP_UNCOMMON_HEADERS 1) endif() +if (LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + set(LWS_WITH_GENCRYPTO 1) +endif() + if (APPLE) set(LWS_ROLE_DBUS 0) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 90cecc8be..cfcc1f049 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ option(LWS_WITH_SECURE_STREAMS_CPP "Secure Streams C++ classes" OFF) option(LWS_WITH_SECURE_STREAMS_PROXY_API "Secure Streams support to work across processes" OFF) option(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM "Auth support for api.amazon.com" OFF) option(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY "Secure Streams Policy is hardcoded only" OFF) +option(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4 "Secure Streams Auth support for AWS Sigv4" OFF) # # CTest options diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 55b367943..e34e80139 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -173,6 +173,7 @@ #cmakedefine LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM #cmakedefine LWS_WITH_SECURE_STREAMS_PROXY_API #cmakedefine LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY +#cmakedefine LWS_WITH_SECURE_STREAMS_AUTH_SIGV4 #cmakedefine LWS_WITH_SELFTESTS #cmakedefine LWS_WITH_SEQUENCER #cmakedefine LWS_WITH_SERVER_STATUS diff --git a/include/libwebsockets/lws-secure-streams-policy.h b/include/libwebsockets/lws-secure-streams-policy.h index 4b58cbdf0..d53408f62 100644 --- a/include/libwebsockets/lws-secure-streams-policy.h +++ b/include/libwebsockets/lws-secure-streams-policy.h @@ -188,6 +188,7 @@ typedef struct lws_ss_auth { struct lws_ss_auth *next; const char *name; + const char *type; const char *streamtype; uint8_t blob_index; } lws_ss_auth_t; @@ -289,6 +290,11 @@ typedef struct lws_ss_policy { const void *plugins_info[2]; /**< plugin-specific data */ #endif +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + /* directly point to the metadata name, no need to expand */ + const char *aws_region; + const char *aws_service; +#endif /* * We're either a client connection policy that wants a trust store, * or we're a server policy that wants a mem cert and key... Hold diff --git a/include/libwebsockets/lws-secure-streams.h b/include/libwebsockets/lws-secure-streams.h index 26e861bbb..11c9ad075 100644 --- a/include/libwebsockets/lws-secure-streams.h +++ b/include/libwebsockets/lws-secure-streams.h @@ -731,5 +731,47 @@ lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h); LWS_VISIBLE LWS_EXTERN const char * lws_ss_tag(struct lws_ss_handle *h); + +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) +/** + * lws_ss_sigv4_set_aws_key() - set aws credential into system blob + * + * \param context: lws_context + * \param idx: the system blob index specified in the policy, currently + * up to 4 blobs. + * \param keyid: aws access keyid + * \param key: aws access key + * + * Return 0 if OK or nonzero if e.g. idx is invalid; system blob heap appending + * fails. + */ + +LWS_VISIBLE LWS_EXTERN int +lws_ss_sigv4_set_aws_key(struct lws_context* context, uint8_t idx, + const char * keyid, const char * key); + +/** + * lws_aws_filesystem_credentials_helper() - read aws credentials from file + * + * \param path: path to read, ~ at start is converted to $HOME contents if any + * \param kid: eg, "aws_access_key_id" + * \param ak: eg, "aws_secret_access_key" + * \param aws_keyid: pointer to pointer for allocated keyid from credentials file + * \param aws_key: pointer to pointer for allocated key from credentials file + * + * Return 0 if both *aws_keyid and *aws_key allocated from the config file, else + * nonzero, and neither *aws_keyid or *aws_key are allocated. + * + * If *aws_keyid and *aws_key are set, it's the user's responsibility to + * free() them when they are no longer needed. + */ + +LWS_VISIBLE LWS_EXTERN int +lws_aws_filesystem_credentials_helper(const char *path, const char *kid, + const char *ak, char **aws_keyid, + char **aws_key); + +#endif + ///@} diff --git a/include/libwebsockets/lws-system.h b/include/libwebsockets/lws-system.h index db1821c65..f3b216c4d 100644 --- a/include/libwebsockets/lws-system.h +++ b/include/libwebsockets/lws-system.h @@ -42,6 +42,15 @@ typedef enum { LWS_SYSBLOB_TYPE_MQTT_USERNAME, LWS_SYSBLOB_TYPE_MQTT_PASSWORD, +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + /* extend 4 more auth blobs, each has 2 slots */ + LWS_SYSBLOB_TYPE_EXT_AUTH1, + LWS_SYSBLOB_TYPE_EXT_AUTH2 = LWS_SYSBLOB_TYPE_EXT_AUTH1 + 2, + LWS_SYSBLOB_TYPE_EXT_AUTH3 = LWS_SYSBLOB_TYPE_EXT_AUTH2 + 2, + LWS_SYSBLOB_TYPE_EXT_AUTH4 = LWS_SYSBLOB_TYPE_EXT_AUTH3 + 2, + LWS_SYSBLOB_TYPE_EXT_AUTH4_1, +#endif + LWS_SYSBLOB_TYPE_COUNT /* ... always last */ } lws_system_blob_item_t; diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index f69963680..ab8c0ca4a 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -1037,11 +1037,13 @@ lws_vhost_destroy1(struct lws_vhost *vh) * swap it to a vhost that has the same * iface + port, but is not closing. */ - assert(v->lserv_wsi == NULL); - v->lserv_wsi = vh->lserv_wsi; lwsl_notice("%s: listen skt from %s to %s\n", - __func__, vh->name, v->name); + __func__, lws_vh_tag(vh), + lws_vh_tag(v)); + + assert(v->lserv_wsi == NULL); + v->lserv_wsi = vh->lserv_wsi; if (v->lserv_wsi) { lws_vhost_unbind_wsi(vh->lserv_wsi); diff --git a/lib/secure-streams/CMakeLists.txt b/lib/secure-streams/CMakeLists.txt index 38e73c419..2513e3be2 100644 --- a/lib/secure-streams/CMakeLists.txt +++ b/lib/secure-streams/CMakeLists.txt @@ -78,6 +78,13 @@ if (LWS_WITH_CLIENT) ) endif() + if (LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + list(APPEND SOURCES + secure-streams/system/auth-sigv4/sign.c + ) + endif() + + if (LWS_WITH_SECURE_STREAMS_CPP) list(APPEND SOURCES secure-streams/cpp/lss.cxx) diff --git a/lib/secure-streams/README.md b/lib/secure-streams/README.md index cbc3794ba..aabea6d86 100644 --- a/lib/secure-streams/README.md +++ b/lib/secure-streams/README.md @@ -217,18 +217,23 @@ auth token blob indexes. ``` ... - "auth": [{"name":"lwa","streamtype":"api_amazon_com_lwa","blob":0}] + "auth": [{"name":"newauth","type":"sigv4", "blob":0}] ... ``` Streams can indicate they depend on a valid auth token from one of these schemes by using the `"use_auth": "name"` member in the streamtype definition, where name -is, eg, "lwa" in the example above. +is, eg, "sigv4" in the example above. If "use_auth" is not in the streamtype +definition, default auth is lwa if "http_auth_header" is there. ### `auth[].name` This is the name of the authentication scheme used by other streamtypes +### `auth[].type` + +Indicate the auth type, e.g. sigv4 + ### `auth[].streamtype` This is the auth streamtype to be used to refresh the authentication token @@ -236,7 +241,8 @@ This is the auth streamtype to be used to refresh the authentication token ### `auth[].blob` This is the auth blob index the authentication token is stored into and retreived -from +from system blob, currently up to 4 blobs. + ### `s` @@ -341,6 +347,12 @@ to validate the remote server cert. Indicate that the streamtype should use the named auth type from the `auth` array in the policy +### `aws_region` +Indicate which metadata should be used to set aws region for certain streamtype + +### `aws_service` +Indicate which metadata should be used to set aws service for certain streamtype + ### `server_cert` **SERVER ONLY**: subject to change... the name of the x.509 cert that is the @@ -742,7 +754,7 @@ proxy to get to the SS proxy, which then goes out to the internet ### 1 Start the SS proxy -Tell it to listen on lo interface on port 1234 +Tell it to listen on lo interface on port 1234 ``` $ ./bin/lws-minimal-secure-streams-proxy -p 1234 -i lo diff --git a/lib/secure-streams/policy-json.c b/lib/secure-streams/policy-json.c index 3b75978fe..5f14f7c6d 100644 --- a/lib/secure-streams/policy-json.c +++ b/lib/secure-streams/policy-json.c @@ -101,8 +101,11 @@ static const char * const lejp_tokens_policy[] = { "s[].*.mqtt_will_retain", "s[].*.swake_validity", "s[].*.use_auth", + "s[].*.aws_region", + "s[].*.aws_service", "s[].*", "auth[].name", + "auth[].type", "auth[].streamtype", "auth[].blob", "auth[]", @@ -181,8 +184,11 @@ typedef enum { LSSPPT_MQTT_WILL_RETAIN, LSSPPT_SWAKE_VALIDITY, LSSPPT_USE_AUTH, + LSSPPT_AWS_REGION, + LSSPPT_AWS_SERVICE, LSSPPT_STREAMTYPES, LSSPPT_AUTH_NAME, + LSSPPT_AUTH_TYPE, LSSPPT_AUTH_STREAMTYPE, LSSPPT_AUTH_BLOB, LSSPPT_AUTH, @@ -621,7 +627,6 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) case LSSPPT_AUTH_BLOB: a->curr[LTY_AUTH].a->blob_index = (uint8_t)atoi(ctx->buf); break; - case LSSPPT_HTTP_EXPECT: a->curr[LTY_POLICY].p->u.http.resp_expect = (uint16_t)atoi(ctx->buf); break; @@ -713,6 +718,7 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) } break; + case LSSPPT_METADATA_ITEM: pmd = a->curr[LTY_POLICY].p->metadata; a->curr[LTY_POLICY].p->metadata = lwsac_use_zero(&a->ac, @@ -809,11 +815,23 @@ lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason) case LSSPPT_AUTH_STREAMTYPE: pp = (char **)&a->curr[LTY_AUTH].a->streamtype; goto string2; - + case LSSPPT_AUTH_TYPE: + pp = (char **)&a->curr[LTY_AUTH].a->type; + goto string2; case LSSPPT_HTTP_FAIL_REDIRECT: a->curr[LTY_POLICY].p->u.http.fail_redirect = reason == LEJPCB_VAL_TRUE; break; +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + case LSSPPT_AWS_REGION: + pp = (char **)&a->curr[LTY_POLICY].p->aws_region; + goto string2; + + case LSSPPT_AWS_SERVICE: + pp = (char **)&a->curr[LTY_POLICY].p->aws_service; + goto string2; +#endif + #endif #if defined(LWS_ROLE_WS) diff --git a/lib/secure-streams/private-lib-secure-streams.h b/lib/secure-streams/private-lib-secure-streams.h index 5860b9f9d..1014ac649 100644 --- a/lib/secure-streams/private-lib-secure-streams.h +++ b/lib/secure-streams/private-lib-secure-streams.h @@ -464,6 +464,11 @@ lws_ss_policy_unref_trust_store(struct lws_context *context, int lws_ss_sys_cpd(struct lws_context *cx); +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) +int lws_ss_apply_sigv4(struct lws *wsi, struct lws_ss_handle *h, + unsigned char **p, unsigned char *end); +#endif + typedef int (* const secstream_protocol_connect_munge_t)(lws_ss_handle_t *h, char *buf, size_t len, struct lws_client_connect_info *i, union lws_ss_contemp *ct); diff --git a/lib/secure-streams/protocols/ss-h1.c b/lib/secure-streams/protocols/ss-h1.c index c573241e6..be8b98bb0 100644 --- a/lib/secure-streams/protocols/ss-h1.c +++ b/lib/secure-streams/protocols/ss-h1.c @@ -616,6 +616,14 @@ malformed: if (!h->policy->u.http.blob_header[m]) continue; + /* + * To be backward compatible, default is system-wide LWA auth, + * and "http_auth_header" is for default LWA auth, current users do not + * need any change in their policy. + * If user wants different auth/token, need to specify the "use_auth" + * and will be handled after metadata headers are applied. + */ + if (m == LWSSS_HBI_AUTH && h->policy->u.http.auth_preamble) o = lws_snprintf((char *)buf, sizeof(buf), "%s", @@ -649,9 +657,19 @@ malformed: if (lws_apply_metadata(h, wsi, buf, p, end)) return -1; +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + if (h->policy->auth && h->policy->auth->type && + !strcmp(h->policy->auth->type, "sigv4")) { + + if (lws_ss_apply_sigv4(wsi, h, p, end)) + return -1; + } +#endif + + (void)oin; - // if (*p != oin) - // lwsl_hexdump_notice(oin, lws_ptr_diff(*p, oin)); + //if (*p != oin) + // lwsl_hexdump_notice(oin, lws_ptr_diff_size_t(*p, oin)); } diff --git a/lib/secure-streams/system/auth-sigv4/sign.c b/lib/secure-streams/system/auth-sigv4/sign.c new file mode 100644 index 000000000..a5b59ce63 --- /dev/null +++ b/lib/secure-streams/system/auth-sigv4/sign.c @@ -0,0 +1,547 @@ +/* + * Sigv4 support for Secure Streams + * + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2020 Andy Green + * securestreams-dev@amazon.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +struct sigv4_header { + const char * name; + const char * value; +}; + +#define MAX_HEADER_NUM 8 +struct sigv4 { + struct sigv4_header headers[MAX_HEADER_NUM]; + uint8_t hnum; + char ymd[10]; /*YYYYMMDD*/ + const char *timestamp; + const char *payload_hash; + const char *region; + const char *service; +}; + +static const uint8_t blob_idx[] = { + LWS_SYSBLOB_TYPE_EXT_AUTH1, + LWS_SYSBLOB_TYPE_EXT_AUTH2, + LWS_SYSBLOB_TYPE_EXT_AUTH3, + LWS_SYSBLOB_TYPE_EXT_AUTH4, +}; + +enum { + LWS_SS_SIGV4_KEYID, + LWS_SS_SIGV4_KEY, + LWS_SS_SIGV4_BLOB_SLOTS +}; + +static inline int add_header(struct sigv4 *s, const char *name, const char *value) +{ + if (s->hnum >= MAX_HEADER_NUM) { + lwsl_err("%s too many sigv4 headers\n", __func__); + return -1; + } + + s->headers[s->hnum].name = name; + s->headers[s->hnum].value = value; + s->hnum++; + + if (!strncmp(name, "x-amz-content-sha256", strlen("x-amz-content-sha256"))) + s->payload_hash = value; + + if (!strncmp(name, "x-amz-date", strlen("x-amz-date"))) { + s->timestamp = value; + strncpy(s->ymd, value, 8); + } + + return 0; +} + +static int +cmp_header(const void * a, const void * b) +{ + return strcmp(((struct sigv4_header *)a)->name, + ((struct sigv4_header *)b)->name); +} + +static int +init_sigv4(struct lws *wsi, struct lws_ss_handle *h, struct sigv4 *s) +{ + lws_ss_metadata_t *polmd = h->policy->metadata; + int m = 0; + + add_header(s, "host:", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + + while (polmd) { + if (polmd->value__may_own_heap && + ((uint8_t *)polmd->value__may_own_heap)[0] && + h->metadata[m].value__may_own_heap) { + /* consider all headers start with "x-amz-" need to be signed */ + if (!strncmp(polmd->value__may_own_heap, "x-amz-", + strlen("x-amz-"))) { + if (add_header(s, polmd->value__may_own_heap, + h->metadata[m].value__may_own_heap)) + return -1; + } + } + if (!strcmp(h->metadata[m].name, h->policy->aws_region) && + h->metadata[m].value__may_own_heap) + s->region = h->metadata[m].value__may_own_heap; + + if (!strcmp(h->metadata[m].name, h->policy->aws_service) && + h->metadata[m].value__may_own_heap) + s->service = h->metadata[m].value__may_own_heap; + + m++; + polmd = polmd->next; + } + + qsort(s->headers, s->hnum, sizeof(struct sigv4_header), cmp_header); + +#if 0 + do { + int i; + for (i= 0; ihnum; i++) + lwsl_debug("%s hdr %s %s\n", __func__, + s->headers[i].name, s->headers[i].value); + + lwsl_debug("%s service: %s region: %s\n", __func__, + s->service, s->region); + } while(0); +#endif + + return 0; +} + +static void +bin2hex(uint8_t *in, size_t len, char *out) +{ + static const char *hex = "0123456789abcdef"; + size_t n; + + for (n = 0; n < len; n++) { + *out++ = hex[(in[n] >> 4) & 0xf]; + *out++ = hex[in[n] & 15]; + } + *out = '\0'; +} + +static int +sha256hash(uint8_t *data, size_t len, char *out) +{ + struct lws_genhash_ctx hash_ctx; + uint8_t hash_bin[32]; + + if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) || + lws_genhash_update(&hash_ctx, (void *)data, len) || + lws_genhash_destroy(&hash_ctx, hash_bin)) + { + + lws_genhash_destroy(&hash_ctx, NULL); + lwsl_err("%s lws_genhash error \n", __func__); + return -1; + } + + bin2hex(hash_bin, sizeof(hash_bin), out); + + return 0; +} + +static int +hmacsha256(const uint8_t *key, size_t keylen, const uint8_t *txt, + size_t txtlen, uint8_t *digest) +{ + struct lws_genhmac_ctx hmacctx; + + if (lws_genhmac_init(&hmacctx, LWS_GENHMAC_TYPE_SHA256, + key, keylen)) + return -1; + + if (lws_genhmac_update(&hmacctx, txt, txtlen)) { + lwsl_err("%s: hmac computation failed\n", __func__); + lws_genhmac_destroy(&hmacctx, NULL); + return -1; + } + + if (lws_genhmac_destroy(&hmacctx, digest)) { + lwsl_err("%s: problem destroying hmac\n", __func__); + return -1; + } + + return 0; +} + +static int +build_sign_string(struct lws *wsi, char *buf, size_t bufsz, + struct lws_ss_handle *h, struct sigv4 *s) +{ + char hash[65], *end = &buf[bufsz - 1], *start; + int i; + + start = buf; + + /* + * build canonical_request and hash it + */ + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n%s\n", + h->policy->u.http.method, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + /* TODO, append query string */ + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "\n"); + for (i = 0; i < s->hnum; i++) { + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s%s\n", + s->headers[i].name, s->headers[i].value); + } + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "\n"); + for (i = 0; i < s->hnum; i++) { + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", + s->headers[i].name); + buf--; /* remove ':' */ + *buf++ = ';'; + } + buf--; /* remove the trailing ';' */ + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "\n%s", + s->payload_hash); + *buf++ = '\0'; + + assert(buf <= start + bufsz); + + sha256hash((uint8_t *)start, strlen(start), hash); + + /* + * build sign string like the following + * + * "AWS4-HMAC-SHA256" + "\n" + + * timeStampISO8601Format + "\n" + + * date.Format() + "/" + + "/" + + "/aws4_request" + "\n" + + * Hex(SHA256Hash()) + */ + buf = start; + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n", + "AWS4-HMAC-SHA256"); + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n", + s->timestamp); + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s/%s/%s/%s\n", + s->ymd, s->region, s->service, "aws4_request"); + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", hash); + *buf++ = '\0'; + + assert(buf <= start + bufsz); + + return 0; +} + +/* + * DateKey = HMAC-SHA256("AWS4"+"", "") + * DateRegionKey = HMAC-SHA256(, "") + * DateRegionServiceKey = HMAC-SHA256(, "") + * SigningKey = HMAC-SHA256(, "aws4_request") + */ +static int +calc_signing_key(struct lws *wsi, struct lws_ss_handle *h, + struct sigv4 *s, uint8_t *sign_key) +{ + uint8_t key[128], date_key[32], and_region_key[32], + and_service_key[32], *kb; + lws_system_blob_t *ab; + size_t keylen; + int n; + + ab = lws_system_get_blob(wsi->a.context, + blob_idx[h->policy->auth->blob_index], + LWS_SS_SIGV4_KEY); + if (!ab) + return -1; + + kb = key; + + *kb++ = 'A'; + *kb++ = 'W'; + *kb++ = 'S'; + *kb++ = '4'; + + keylen = sizeof(key) - 4; + if (lws_system_blob_get_size(ab) > keylen - 1) + return -1; + + n = lws_system_blob_get(ab, kb, &keylen, 0); + if (n < 0) + return -1; + + kb[keylen] = '\0'; + + hmacsha256((const uint8_t *)key, strlen((const char *)key), + (const uint8_t *)s->ymd, strlen(s->ymd), date_key); + + hmacsha256(date_key, sizeof(date_key), (const uint8_t *)s->region, + strlen(s->region), and_region_key); + + hmacsha256(and_region_key, sizeof(and_region_key), + (const uint8_t *)s->service, + strlen(s->service), and_service_key); + + hmacsha256(and_service_key, sizeof(and_service_key), + (uint8_t *)"aws4_request", + strlen("aws4_request"), sign_key); + + return 0; +} + +/* Sample auth string: + * + * 'Authorization: AWS4-HMAC-SHA256 Credential=AKIAVHWASOFE7TJ7ZUQY/20200731/us-west-2/s3/aws4_request, +* SignedHeaders=host;x-amz-content-sha256;x-amz-date, \ +* Signature=ad9fb75ff3b46c7990e3e8f090abfdd6c01fd67761a517111694377e20698377' +*/ +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) +{ + char *start = buf, *end = &buf[bufsz - 1]; + char *c; + lws_system_blob_t *ab; + size_t keyidlen = 128; // max keyid len is 128 + int n; + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", + "AWS4-HMAC-SHA256 "); + + ab = lws_system_get_blob(wsi->a.context, + blob_idx[h->policy->auth->blob_index], + LWS_SS_SIGV4_KEYID); + if (!ab) + return -1; + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", + "Credential="); + n = lws_system_blob_get(ab,(uint8_t *)buf, &keyidlen, 0); + if (n < 0) + return -1; + buf += keyidlen; + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "/%s/%s/%s/%s, ", + s->ymd, s->region, s->service, "aws4_request"); + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", + "SignedHeaders="); + for (n = 0; n < s->hnum; n++) { + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), + "%s",s->headers[n].name); + buf--; /* remove ':' */ + *buf++ = ';'; + } + c = buf - 1; + *c = ','; /* overwrite ';' back to ',' */ + + buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), + "%s", " Signature="); + bin2hex(signature_bin, 32, buf); + + assert(buf+65 <= start + bufsz); + + lwsl_debug("%s %s\n", __func__, start); + + return 0; + +} + +int +lws_ss_apply_sigv4(struct lws *wsi, struct lws_ss_handle *h, + unsigned char **p, unsigned char *end) +{ + uint8_t buf[512], sign_key[32], signature_bin[32], *bp; + struct sigv4 s; + + memset(&s, 0, sizeof(s)); + + bp = buf; + + init_sigv4(wsi, h, &s); + if (!s.timestamp || !s.payload_hash) { + lwsl_err("%s missing headers\n", __func__); + return -1; + } + + if (build_sign_string(wsi, (char *)bp, sizeof(buf), h, &s)) + return -1; + + if (calc_signing_key(wsi, h, &s, sign_key)) + return -1; + + hmacsha256(sign_key, sizeof(sign_key), (const uint8_t *)buf, + strlen((const char *)buf), signature_bin); + + bp = buf; /* reuse for auth_str */ + if (build_auth_string(wsi, (char *)bp, sizeof(buf), h, &s, + signature_bin)) + return -1; + + if (lws_add_http_header_by_name(wsi, + (const uint8_t *)"Authorization:", buf, + (int)strlen((const char*)buf), p, end)) + return -1; + + return 0; +} + +int +lws_ss_sigv4_set_aws_key(struct lws_context* context, uint8_t idx, + const char * keyid, const char * key) +{ + const char * s[] = { keyid, key }; + lws_system_blob_t *ab; + int i; + + if (idx > LWS_ARRAY_SIZE(blob_idx)) + return -1; + + for (i = 0; i < LWS_SS_SIGV4_BLOB_SLOTS; i++) { + ab = lws_system_get_blob(context, blob_idx[idx], i); + if (!ab) + return -1; + + lws_system_blob_heap_empty(ab); + + if (lws_system_blob_heap_append(ab, (const uint8_t *)s[i], + strlen(s[i]))) { + lwsl_err("%s: can't store %d \n", __func__, i); + + return -1; + } + } + + return 0; +} + +#if defined(__linux__) || defined(__APPLE__) || defined(WIN32) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(__ANDROID__) + +/* ie, if we have filesystem ops */ + +int +lws_aws_filesystem_credentials_helper(const char *path, const char *kid, + const char *ak, char **aws_keyid, + char **aws_key) +{ + char *str = NULL, *val = NULL, *line = NULL, sth[128]; + size_t len = sizeof(sth); + const char *home = ""; + int i, poff = 0; + ssize_t rd; + FILE *fp; + + *aws_keyid = *aws_key = NULL; + + if (path[0] == '~') { + home = getenv("HOME"); + if (home && strlen(home) > sizeof(sth) - 1) /* coverity */ + return -1; + else { + if (!home) + home = ""; + + poff = 1; + } + } + lws_snprintf(sth, sizeof(sth), "%s%s", home, path + poff); + + fp = fopen(sth, "r"); + if (!fp) { + lwsl_err("%s can't open '%s'\n", __func__, sth); + + return -1; + } + + while ((rd = getline(&line, &len, fp)) != -1) { + for (i = 0; i < 2; i++) { + size_t slen; + + if (strncmp(line, i ? kid : ak, strlen(i ? kid : ak))) + continue; + + str = strchr(line, '='); + if (!str) + continue; + + str++; + + /* only read the first key for each */ + if (*(i ? aws_keyid : aws_key)) + continue; + + /* + * Trim whitespace from the start and end + */ + + slen = (size_t)(rd - lws_ptr_diff(str, line)); + + while (slen && *str == ' ') { + str++; + slen--; + } + + while (slen && (str[slen - 1] == '\r' || + str[slen - 1] == '\n' || + str[slen - 1] == ' ')) + slen--; + + val = malloc(slen + 1); + if (!val) + goto bail; + + strncpy(val, str, slen); + val[slen] = '\0'; + + *(i ? aws_keyid : aws_key) = val; + + } + } + +bail: + fclose(fp); + + if (line) + free(line); + + if (!*aws_keyid || !*aws_key) { + if (*aws_keyid) { + free(*aws_keyid); + *aws_keyid = NULL; + } + if (*aws_key) { + free(*aws_key); + *aws_key = NULL; + } + lwsl_err("%s can't find aws credentials! \ + please check %s\n", __func__, path); + return -1; + } + + lwsl_info("%s: '%s' '%s'\n", __func__, *aws_keyid, *aws_key); + + return 0; +} +#endif diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c index c3e1c6fdc..d569f36f9 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c @@ -177,6 +177,11 @@ static const char *canned_root_token_payload = "&client_id=" "amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d"; +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) +static char *aws_keyid = NULL, + *aws_key = NULL; +#endif + static int app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, int current, int target) @@ -203,7 +208,18 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, strlen(canned_root_token_payload)); break; case LWS_SYSTATE_OPERATIONAL: - if (current == LWS_SYSTATE_OPERATIONAL) + if (current == LWS_SYSTATE_OPERATIONAL) { +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + + if (lws_aws_filesystem_credentials_helper( + "~/.aws/credentials", + "aws_access_key_id", + "aws_secret_access_key", + &aws_keyid, &aws_key)) + return -1; + + lws_ss_sigv4_set_aws_key(context, 0, aws_keyid, aws_key); +#endif /* * At this point we have DHCP, ntp, system auth token * and we can reasonably create the proxy @@ -213,6 +229,7 @@ app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, __func__); return -1; } + } break; case LWS_SYSTATE_POLICY_INVALID: /* @@ -315,6 +332,13 @@ int main(int argc, const char **argv) bad = 0; +#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4) + if (aws_keyid) + free(aws_keyid); + if (aws_key) + free(aws_key); +#endif + lws_context_destroy(context); lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sigv4/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/CMakeLists.txt new file mode 100644 index 000000000..da80d5eed --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/CMakeLists.txt @@ -0,0 +1,102 @@ +project(lws-minimal-secure-streams-sigv4 C) +cmake_minimum_required(VERSION 2.8.12) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-secure-streams-sigv4) + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4 1 requirements) + +if (requirements) + add_executable(${SAMP} ss-s3-main.c ss-s3-ss.c) + + find_program(VALGRIND "valgrind") + + if (LWS_CTEST_INTERNET_AVAILABLE AND NOT WIN32) + if (VALGRIND) + message("testing via valgrind") + add_test(NAME ss-sigv4 COMMAND + ${VALGRIND} --tool=memcheck --leak-check=yes --num-callers=20 + $) + else() + add_test(NAME ss-sigv4 COMMAND lws-minimal-secure-streams-sigv4) + endif() + + set_tests_properties(ss-sigv4 + PROPERTIES + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/secure-streams/minimal-secure-streams-sigv4 + TIMEOUT 20) + + if (HAS_LWS_WITH_SECURE_STREAMS_PROXY_API OR LWS_WITH_SECURE_STREAMS_PROXY_API) + + # + # Define test dep to bring up and take down the test + # proxy + # + + if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + # uds abstract namespace for linux + set(CTEST_SOCKET_PATH "@ctest-sspsigv4-$ENV{SAI_PROJECT}-$ENV{SAI_OVN}") + else() + # filesystem socket for others + set(CTEST_SOCKET_PATH "/tmp/ctest-sspsigv4-$ENV{SAI_PROJECT}-$ENV{SAI_OVN}") + endif() + add_test(NAME st_ssproxysigv4 COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background.sh + ssproxysigv4 $ + -i ${CTEST_SOCKET_PATH} --ignore-sigterm) + set_tests_properties(st_ssproxysigv4 PROPERTIES WORKING_DIRECTORY . FIXTURES_SETUP ssproxysigv4 TIMEOUT 800) + + add_test(NAME ki_ssproxysigv4 COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background-kill.sh + ssproxysigv4 $ + -i ${CTEST_SOCKET_PATH}) + set_tests_properties(ki_ssproxysigv4 PROPERTIES FIXTURES_CLEANUP ssproxysigv4) + + # + # the client part that will connect to the proxy + # + + if (VALGRIND) + message("testing via valgrind") + add_test(NAME sspc-sigv4 COMMAND + ${VALGRIND} --tool=memcheck --leak-check=yes --num-callers=20 + $ -i +${CTEST_SOCKET_PATH}) + else() + add_test(NAME sspc-sigv4 COMMAND lws-minimal-secure-streams-sigv4-client -i +${CTEST_SOCKET_PATH}) + endif() + set_tests_properties(sspc-sigv4 PROPERTIES + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/secure-streams/minimal-secure-streams-sigv4 + FIXTURES_REQUIRED "ssproxysigv4" + TIMEOUT 40) + + endif() + + endif() + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() + + if (LWS_WITH_SECURE_STREAMS_PROXY_API) + add_compile_options(-DLWS_SS_USE_SSPC) + + add_executable(${SAMP}-client ss-s3-main.c ss-s3-ss.c) + if (websockets_shared) + target_link_libraries(${SAMP}-client websockets_shared) + add_dependencies(${SAMP}-client websockets_shared) + else() + target_link_libraries(${SAMP}-client websockets) + endif() + endif() + +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sigv4/README.md b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/README.md new file mode 100644 index 000000000..15b1051a6 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/README.md @@ -0,0 +1,46 @@ +# lws minimal secure streams sigv4 + +The application put a test file to AWS S3, using sigv4 auth. + +It does it using Secure Streams... the streamtype is "s3PutObj", along with main +are in ss-s3-main.c + +The handler for state changes and payloads for "s3PutObj" is in ss-s3-ss.c + + +## metadata + "aws_region" and "aws_service" are configured through metadata. Also, at least + "x-amz-content-sha256:" and ""x-amz-date:" headers need to be in metadata. + + +## credentials +credentials are read from ~/.aws/credentials, make sure you have valid keyid and +key. One need to call lws_ss_sigv4_set_aws_key() to plug in aws credentials into +Secure Streams and the index need to be match of the "blob_index" in entry of "auth" +the policy. In addition, you need to change the S3 bucket name to your own, as +bucket name is unique globally in S3. + + +## build + +``` + $ cmake . && make +``` + +## usage + + +``` +[2020/12/19 15:25:06:9763] U: LWS minimal secure streams sigv4 +[2020/12/19 15:25:07:0768] U: ss_s3_state: LWSSSCS_CREATING, ord 0x0 +[2020/12/19 15:25:07:0769] U: ss_s3_state: LWSSSCS_POLL, ord 0x0 +[2020/12/19 15:25:07:0770] U: ss_s3_state: LWSSSCS_CONNECTING, ord 0x0 +[2020/12/19 15:25:07:2317] U: SS / TX Payload +[2020/12/19 15:25:07:2317] U: SS / TX Payload Total = 1024, Pos = 0 +[2020/12/19 15:25:07:3267] U: ss_s3_state: LWSSSCS_CONNECTED, ord 0x0 +[2020/12/19 15:25:07:3267] U: ss_s3_state: LWSSSCS_QOS_ACK_REMOTE, ord 0x0 +[2020/12/19 15:25:07:3267] U: ss_s3_state: LWSSSCS_DISCONNECTED, ord 0x0 +[2020/12/19 15:25:07:3268] U: ss_s3_state: LWSSSCS_DESTROYING, ord 0x0 +[2020/12/19 15:25:07:3269] U: Completed: OK + +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c new file mode 100644 index 000000000..3d88dc45d --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-main.c @@ -0,0 +1,245 @@ +/* + * S3 Put Object via Secure Streams minimal sigv4 example + * + * Written in 2010-2020 by Andy Green + * Amit Pachore + * securestreams-dev@amazon.com + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include +#include + +#include "ss-s3-put.h" + +int interrupted, bad = 1; +static lws_state_notify_link_t nl; +extern const lws_ss_info_t s3_ssi; + +#if !defined(LWS_SS_USE_SSPC) + +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," + + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "100," + "200," + "300," + "500," + "1000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "30," + "\"svalidhup\":" "35" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + "{\"baltimore_cybertrust_root\": \"" /* LE X3 signed by ISRG X1 root */ + "MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ" + "RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD" + "VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX" + "DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y" + "ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy" + "VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr" + "mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr" + "IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK" + "mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu" + "XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy" + "dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye" + "jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1" + "BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3" + "DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92" + "9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx" + "jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0" + "Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz" + "ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS" + "R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"s3-root-cert\"," + "\"stack\": [" + "\"baltimore_cybertrust_root\"" + "]" + "}" + "]," + "\"auth\": [" /* named cert chains */ + "{" + "\"name\": \"sigv4_brahms\"," + "\"type\": \"sigv4\"," + "\"blob\": 0" + "}" + + "]," + "\"s\": [" + "{\"s3PutObj\": {" + "\"endpoint\":" "\"${s3bucket}.s3.amazonaws.com\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"PUT\"," + "\"http_url\":" "\"${s3Obj}\"," + "\"http_no_content_length\": false," + "\"tls\":" "true," + "\"tls_trust_store\":" "\"s3-root-cert\"," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"use_auth\":" "\"sigv4_brahms\"," + "\"aws_region\":" "\"region\"," + "\"aws_service\":" "\"service\"," + "\"metadata\": [" + "{\"region\": \"\"}," + "{\"service\": \"\"}," + "{\"s3bucket\": \"\"}," + "{\"s3Obj\": \"\"}," + "{\"ctype\": \"content-type:\"}," + "{\"xcsha256\": \"x-amz-content-sha256:\"}," + "{\"xdate\": \"x-amz-date:\"}," + "{\"xacl\": \"x-amz-acl:\"}" + "]" + "}}" + "]" + "}" +; + +static char *aws_keyid, *aws_key; +#endif + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + struct lws_ss_handle *h; + + switch (target) { + case LWS_SYSTATE_REGISTERED: + break; + + case LWS_SYSTATE_OPERATIONAL: + if (current != LWS_SYSTATE_OPERATIONAL) + break; + +#if !defined(LWS_SS_USE_SSPC) + if (lws_aws_filesystem_credentials_helper( + "~/.aws/credentials", + "aws_access_key_id", + "aws_secret_access_key", + &aws_keyid, &aws_key)) + return -1; + lws_ss_sigv4_set_aws_key(context, 0, aws_keyid, aws_key); +#endif + + if (lws_ss_create(context, 0, &s3_ssi, NULL, &h, + NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + + return -1; + } + break; + } + + return 0; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN /* | LLL_NOTICE */ ; + struct lws_context_creation_info info; + struct lws_context *context; + int n = 0; + + signal(SIGINT, sigint_handler); + lws_set_log_level(logs, NULL); + + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS minimal secure streams sigv4 \n"); + + info.fd_limit_per_thread = 1 + 6 + 1; + info.port = CONTEXT_PORT_NO_LISTEN; + +#if defined(LWS_SS_USE_SSPC) + info.protocols = lws_sspc_protocols; + { + const char *p; + + /* connect to ssproxy via UDS by default, else via + * tcp connection to this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + info.ss_proxy_port = (uint16_t)atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket + * path; when -p given this can specify the network interface + * to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + info.ss_proxy_bind = p; + + /* if -p given, -a specifies the proxy address to connect to */ + if ((p = lws_cmdline_option(argc, argv, "-a"))) + info.ss_proxy_address = p; + } +#else + info.pss_policies_json = default_ss_policy; + + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; +#endif + + /* integrate us with lws system state management when context created */ + + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the context */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + lws_system_blob_heap_append(lws_system_get_blob(context, + LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0), + (const uint8_t *)"beerfountain", 12); + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + +#if !defined(LWS_SS_USE_SSPC) + if (aws_key) + free(aws_key); + if (aws_keyid) + free(aws_keyid); +#endif + + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-put.h b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-put.h new file mode 100644 index 000000000..ee2e99bb0 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-put.h @@ -0,0 +1,21 @@ +/* + * S3 Put Object via Secure Streams minimal sigv4 example + * + * Written in 2010-2020 by Andy Green + * Amit Pachore + * securestreams-dev@amazon.com + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +typedef struct ss_s3_put { + struct lws_ss_handle *ss; + void *opaque_data; + + /* ... application specific state ... */ + + size_t total; + size_t pos; + uint8_t *buf; +} ss_s3_put_t; diff --git a/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-ss.c b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-ss.c new file mode 100644 index 000000000..e941c6df7 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-sigv4/ss-s3-ss.c @@ -0,0 +1,215 @@ +/* + * S3 Put Object via Secure Streams minimal siv4 example + * + * Written in 2010-2020 by Andy Green + * Amit Pachore + * securestreams-dev@amazon.com + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include +#include "ss-s3-put.h" + +extern int interrupted, bad; + +static lws_ss_state_return_t +ss_s3_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + // ss_s3_put_t *m = (ss_s3_put_t *)userobj; + + if (flags & LWSSS_FLAG_EOM) { + bad = 0; + interrupted = 1; /* this example wants to exit after rx */ + return LWSSSSRET_DESTROY_ME; + } + + lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_err(buf, len); + + return LWSSSSRET_OK; +} + +static lws_ss_state_return_t +ss_s3_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + ss_s3_put_t *m = (ss_s3_put_t *)userobj; + + if (!m->pos) + *flags |= LWSSS_FLAG_SOM; + + lwsl_user("%s: Send... total: %ld, pos: %ld\n", __func__, + (long)m->total, (long)m->pos); + + if (*len > m->total - m->pos) + *len = m->total - m->pos; + + if (!*len) + return LWSSSSRET_TX_DONT_SEND; + + memcpy(buf, m->buf + m->pos, *len); + m->pos += *len; + + if (m->pos == m->total) { + *flags |= LWSSS_FLAG_EOM; + // m->pos = 0; /* we only want to send once */ + } else + lws_ss_request_tx(m->ss); + + return LWSSSSRET_OK; +} + +static const char *awsService = "s3", + *awsRegion = "us-west-2", + *s3bucketName = "sstest2020", + *s3ObjName = "SSs3upload2.txt"; +static char timestamp[32], payload_hash[65]; +static uint8_t jpl[1 * 1024]; + + +static void +create_payload(uint8_t *buf, size_t s) +{ + int i; + + for (i = 0; i < (int)s; i++) + buf[i] = (uint8_t)('a' + i % 16); +} + +static void set_time(char *t) +{ + /*20150830T123600Z*/ + time_t rawtime; + struct tm *info; + + time(&rawtime ); + info = gmtime(&rawtime); + + strftime(t ,20,"%Y%m%dT%H%M%SZ", info); + + return; +} + +static void bin2hex(uint8_t *in, size_t len, char *out) +{ + static const char *hex = "0123456789abcdef"; + size_t n; + + for (n = 0; n < len; n++) { + *out++ = hex[(in[n] >> 4) & 0xf]; + *out++ = hex[in[n] & 15]; + } + *out = '\0'; +} + +static void sigv4_sha256hash_payload(uint8_t *payload, size_t len, char *hash) +{ + struct lws_genhash_ctx hash_ctx; + uint8_t hash_bin[32]; + + if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256) || + /* + * If there is no payload, you must provide the hash of an + * empty string... + */ + lws_genhash_update(&hash_ctx, + payload ? (void *)payload : (void *)"", + payload ? len : 0u) || + lws_genhash_destroy(&hash_ctx, hash_bin)) + { + + lws_genhash_destroy(&hash_ctx, NULL); + lwsl_err("%s lws_genhash failed\n", __func__); + + return; + } + + bin2hex(hash_bin, 32, hash); +} + +static lws_ss_state_return_t +ss_s3_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + ss_s3_put_t *m = (ss_s3_put_t *)userobj; + + lwsl_user("%s: %s %s, ord 0x%x\n", __func__, lws_ss_tag(m->ss), + lws_ss_state_name((int)state), (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_set_metadata(m->ss, "s3bucket", + s3bucketName, strlen(s3bucketName)); + lws_ss_set_metadata(m->ss, "s3Obj", + s3ObjName, strlen(s3ObjName)); + lws_ss_set_metadata(m->ss, "ctype", + "text/plain", strlen("text/plain")); + create_payload(jpl, sizeof(jpl)); + m->buf = (uint8_t *)jpl; + m->total = sizeof(jpl); + + lws_ss_set_metadata(m->ss, "region", + awsRegion, strlen(awsRegion)); + lws_ss_set_metadata(m->ss, "service", + awsService, strlen(awsService)); + + lws_ss_set_metadata(m->ss, "xacl", + "bucket-owner-full-control", + strlen("bucket-owner-full-control")); + + sigv4_sha256hash_payload(m->buf, m->total, payload_hash); + + lws_ss_set_metadata(m->ss, "xcsha256", + payload_hash, strlen(payload_hash)); + + memset(timestamp, 0, sizeof(timestamp)); + set_time(timestamp); + lws_ss_set_metadata(m->ss, "xdate", + timestamp, strlen(timestamp)); + + lws_ss_request_tx_len(m->ss, m->total); + break; + + case LWSSSCS_CONNECTED: + lws_ss_request_tx(m->ss); + break; + + case LWSSSCS_DISCONNECTED: + return LWSSSSRET_DESTROY_ME; + + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + bad = 1; + return LWSSSSRET_DESTROY_ME; + + case LWSSSCS_QOS_ACK_REMOTE: + bad = 0; + break; + + case LWSSSCS_QOS_NACK_REMOTE: + bad = 1; + break; + + case LWSSSCS_DESTROYING: + interrupted = 1; + break; + + default: + break; + } + + return 0; +} + +const lws_ss_info_t s3_ssi = { + .handle_offset = offsetof(ss_s3_put_t, ss), + .opaque_user_data_offset = offsetof(ss_s3_put_t, opaque_data), + .rx = ss_s3_rx, + .tx = ss_s3_tx, + .state = ss_s3_state, + .user_alloc = sizeof(ss_s3_put_t), + .streamtype = "s3PutObj" +}; diff --git a/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt index 7c6ed6cfb..416dc650a 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt +++ b/minimal-examples/secure-streams/minimal-secure-streams/CMakeLists.txt @@ -68,7 +68,7 @@ if (requirements) message("testing via valgrind") add_test(NAME sspc-minimal COMMAND ${VALGRIND} --tool=memcheck --leak-check=yes --num-callers=20 - $ -i +${CTEST_SOCKET_PATH}) + $ -i +${CTEST_SOCKET_PATH}) else() add_test(NAME sspc-minimal COMMAND lws-minimal-secure-streams-client -i +${CTEST_SOCKET_PATH}) endif()