summaryrefslogtreecommitdiffstats
path: root/fluent-bit/src/flb_signv4.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:19:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:20:02 +0000
commit58daab21cd043e1dc37024a7f99b396788372918 (patch)
tree96771e43bb69f7c1c2b0b4f7374cb74d7866d0cb /fluent-bit/src/flb_signv4.c
parentReleasing debian version 1.43.2-1. (diff)
downloadnetdata-58daab21cd043e1dc37024a7f99b396788372918.tar.xz
netdata-58daab21cd043e1dc37024a7f99b396788372918.zip
Merging upstream version 1.44.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fluent-bit/src/flb_signv4.c')
-rw-r--r--fluent-bit/src/flb_signv4.c1245
1 files changed, 1245 insertions, 0 deletions
diff --git a/fluent-bit/src/flb_signv4.c b/fluent-bit/src/flb_signv4.c
new file mode 100644
index 00000000..a4283dc0
--- /dev/null
+++ b/fluent-bit/src/flb_signv4.c
@@ -0,0 +1,1245 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* Fluent Bit
+ * ==========
+ * Copyright (C) 2015-2022 The Fluent Bit Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ *
+ * AWS Signv4 documentation
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+ */
+
+#include <fluent-bit/flb_info.h>
+#include <fluent-bit/flb_kv.h>
+#include <fluent-bit/flb_sds.h>
+#include <fluent-bit/flb_utils.h>
+#include <fluent-bit/flb_hmac.h>
+#include <fluent-bit/flb_hash.h>
+#include <fluent-bit/flb_http_client.h>
+#include <fluent-bit/flb_signv4.h>
+#include <fluent-bit/flb_aws_credentials.h>
+
+#include <stdlib.h>
+#include <ctype.h>
+
+static flb_sds_t sha256_to_hex(unsigned char *sha256)
+{
+ int i;
+ flb_sds_t hex;
+ flb_sds_t tmp;
+
+ hex = flb_sds_create_size(64);
+ if (!hex) {
+ flb_error("[signv4] cannot allocate buffer to convert sha256 to hex");
+ return NULL;
+ }
+
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&hex, "%02x", sha256[i]);
+ if (!tmp) {
+ flb_error("[signedv4] error formatting sha256 -> hex");
+ flb_sds_destroy(hex);
+ return NULL;
+ }
+ hex = tmp;
+ }
+
+ return hex;
+}
+
+static int hmac_sha256_sign(unsigned char out[32],
+ unsigned char *key, size_t key_len,
+ unsigned char *msg, size_t msg_len)
+{
+ int result;
+
+ result = flb_hmac_simple(FLB_HASH_SHA256,
+ key, key_len,
+ msg, msg_len,
+ out, 32);
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int kv_key_cmp(const void *a_arg, const void *b_arg)
+{
+ int ret;
+ struct flb_kv *kv_a = *(struct flb_kv **) a_arg;
+ struct flb_kv *kv_b = *(struct flb_kv **) b_arg;
+
+ ret = strcmp(kv_a->key, kv_b->key);
+ if (ret == 0) {
+ /*
+ * NULL pointer is allowed in kv_a->val and kv_b->val.
+ * Handle NULL pointer cases before passing to strcmp.
+ */
+ if (kv_a->val == NULL && kv_b->val == NULL) {
+ ret = 0;
+ }
+ else if (kv_a->val == NULL) {
+ ret = -1;
+ }
+ else if (kv_b->val == NULL) {
+ ret = 1;
+ }
+ else {
+ ret = strcmp(kv_a->val, kv_b->val);
+ }
+ }
+
+ return ret;
+}
+
+static inline int to_encode(char c)
+{
+ if ((c >= 48 && c <= 57) || /* 0-9 */
+ (c >= 65 && c <= 90) || /* A-Z */
+ (c >= 97 && c <= 122) || /* a-z */
+ (c == '-' || c == '_' || c == '.' || c == '~' || c == '/' ||
+ c == '=')) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+static inline int to_encode_path(char c)
+{
+ if ((c >= 48 && c <= 57) || /* 0-9 */
+ (c >= 65 && c <= 90) || /* A-Z */
+ (c >= 97 && c <= 122) || /* a-z */
+ (c == '-' || c == '_' || c == '.' || c == '~' || c == '/')) {
+ return FLB_FALSE;
+ }
+
+ return FLB_TRUE;
+}
+
+flb_sds_t flb_signv4_uri_normalize_path(char *uri, size_t len)
+{
+ char *p;
+ int end_slash = FLB_FALSE;
+ struct mk_list *tmp;
+ struct mk_list *prev;
+ struct mk_list *head;
+ struct mk_list *split;
+ struct flb_split_entry *entry;
+ flb_sds_t out;
+
+ if (len == 0) {
+ return NULL;
+ }
+
+ out = flb_sds_create_len(uri, len+1);
+ if (!out) {
+ return NULL;
+ }
+ out[len] = '\0';
+
+ if (uri[len - 1] == '/') {
+ end_slash = FLB_TRUE;
+ }
+
+ split = flb_utils_split(out, '/', -1);
+ if (!split) {
+ flb_sds_destroy(out);
+ return NULL;
+ }
+
+ p = out;
+ *p++ = '/';
+
+ mk_list_foreach_safe(head, tmp, split) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ if (entry->len == 1 && *entry->value == '.') {
+ flb_utils_split_free_entry(entry);
+ }
+ else if (entry->len == 2 && memcmp(entry->value, "..", 2) == 0) {
+ prev = head->prev;
+ if (prev != split) {
+ entry = mk_list_entry(prev, struct flb_split_entry, _head);
+ flb_utils_split_free_entry(entry);
+ }
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ flb_utils_split_free_entry(entry);
+ }
+ }
+
+ mk_list_foreach(head, split) {
+ entry = mk_list_entry(head, struct flb_split_entry, _head);
+ memcpy(p, entry->value, entry->len);
+ p += entry->len;
+
+ if (head->next != split) {
+ *p++ = '/';
+ }
+ }
+
+ len = (p - out);
+ if (end_slash == FLB_TRUE && out[len - 1] != '/') {
+ *p++ = '/';
+ }
+
+ flb_utils_split_free(split);
+
+ flb_sds_len_set(out, p - out);
+ out[p - out] = '\0';
+
+ return out;
+}
+
+static flb_sds_t uri_encode(const char *uri, size_t len)
+{
+ int i;
+ flb_sds_t buf = NULL;
+ flb_sds_t tmp = NULL;
+ int is_query_string = FLB_FALSE;
+ int do_encode = FLB_FALSE;
+
+ buf = flb_sds_create_size(len * 2);
+ if (!buf) {
+ flb_error("[signv4] cannot allocate buffer for URI encoding");
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (uri[i] == '?') {
+ is_query_string = FLB_TRUE;
+ }
+ do_encode = FLB_FALSE;
+
+ if (is_query_string == FLB_FALSE && to_encode_path(uri[i]) == FLB_TRUE) {
+ do_encode = FLB_TRUE;
+ }
+ if (is_query_string == FLB_TRUE && to_encode(uri[i]) == FLB_TRUE) {
+ do_encode = FLB_TRUE;
+ }
+ if (do_encode == FLB_TRUE) {
+ tmp = flb_sds_printf(&buf, "%%%02X", (unsigned char) *(uri + i));
+ if (!tmp) {
+ flb_error("[signv4] error formatting special character");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ continue;
+ }
+
+ /* Direct assignment, just copy the character */
+ if (buf) {
+ tmp = flb_sds_cat(buf, uri + i, 1);
+ if (!tmp) {
+ flb_error("[signv4] error composing outgoing buffer");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * Encodes URI parameters, which can not have "/" characters in them
+ * (This happens in an STS request, the role ARN has a slash and is
+ * given as a query parameter).
+ */
+static flb_sds_t uri_encode_params(const char *uri, size_t len)
+{
+ int i;
+ flb_sds_t buf = NULL;
+ flb_sds_t tmp = NULL;
+
+ buf = flb_sds_create_size(len * 2);
+ if (!buf) {
+ flb_error("[signv4] cannot allocate buffer for URI encoding");
+ return NULL;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (to_encode(uri[i]) == FLB_TRUE || uri[i] == '/') {
+ tmp = flb_sds_printf(&buf, "%%%02X", (unsigned char) *(uri + i));
+ if (!tmp) {
+ flb_error("[signv4] error formatting special character");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ continue;
+ }
+
+ /* Direct assignment, just copy the character */
+ if (buf) {
+ tmp = flb_sds_cat(buf, uri + i, 1);
+ if (!tmp) {
+ flb_error("[signv4] error composing outgoing buffer");
+ flb_sds_destroy(buf);
+ return NULL;
+ }
+ buf = tmp;
+ }
+ }
+
+ return buf;
+}
+
+/*
+ * Convert URL encoded params (query string or POST payload) to a sorted
+ * key/value linked list
+ */
+static flb_sds_t url_params_format(char *params)
+{
+ int i;
+ int ret;
+ int len;
+ int items;
+ char *p;
+ struct mk_list list;
+ struct mk_list split;
+ struct mk_list *h_tmp;
+ struct mk_list *head;
+ struct flb_slist_entry *e;
+ flb_sds_t key;
+ flb_sds_t val;
+ flb_sds_t tmp;
+ flb_sds_t buf = NULL;
+ struct flb_kv *kv;
+ struct flb_kv **arr;
+
+ mk_list_init(&list);
+ mk_list_init(&split);
+
+ ret = flb_slist_split_string(&split, params, '&', -1);
+ if (ret == -1) {
+ flb_error("[signv4] error processing given query string");
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ mk_list_foreach_safe(head, h_tmp, &split) {
+ e = mk_list_entry(head, struct flb_slist_entry, _head);
+ p = strchr(e->str, '=');
+ if (!p) {
+ continue;
+ }
+
+ len = (p - e->str);
+ p++;
+
+ /* URI encode every key and value */
+ key = uri_encode_params(e->str, len);
+ len++;
+ val = uri_encode_params(p, flb_sds_len(e->str) - len);
+ if (!key || !val) {
+ flb_error("[signv4] error encoding uri for query string");
+ if (key) {
+ flb_sds_destroy(key);
+ }
+ if (val) {
+ flb_sds_destroy(val);
+ }
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ /*
+ * If key length is 0 then a problem occurs because val
+ * will not be set flb_kv_item_create_len, which eventually
+ * results in issues since kv->val will be equal to NULL.
+ * Thus, check here whether key length is satisfied
+ */
+ if (flb_sds_len(key) == 0) {
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ kv = flb_kv_item_create_len(&list,
+ key, flb_sds_len(key),
+ val, flb_sds_len(val));
+ flb_sds_destroy(key);
+ flb_sds_destroy(val);
+
+ if (!kv) {
+ flb_error("[signv4] error processing key/value from query string");
+ flb_slist_destroy(&split);
+ flb_kv_release(&list);
+ return NULL;
+ }
+ }
+ flb_slist_destroy(&split);
+
+ /* Sort the kv list of parameters */
+ items = mk_list_size(&list);
+ if (items == 0) {
+ flb_kv_release(&list);
+ return flb_sds_create("");
+ }
+
+ arr = flb_calloc(1, sizeof(struct flb_kv *) * items);
+ if (!arr) {
+ flb_errno();
+ flb_kv_release(&list);
+ return NULL;
+ }
+
+ i = 0;
+ mk_list_foreach(head, &list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+ arr[i] = kv;
+ i++;
+ }
+ /* sort headers by key */
+ qsort(arr, items, sizeof(struct flb_kv *), kv_key_cmp);
+
+ /* Format query string parameters */
+ buf = flb_sds_create_size(items * 64);
+ if (!buf) {
+ flb_kv_release(&list);
+ flb_free(arr);
+ return NULL;
+ }
+
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+ if (i + 1 < items) {
+ if (kv->val == NULL) {
+ tmp = flb_sds_printf(&buf, "%s=&",
+ kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&buf, "%s=%s&",
+ kv->key, kv->val);
+ }
+ }
+ else {
+ if (kv->val == NULL) {
+ tmp = flb_sds_printf(&buf, "%s=",
+ kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&buf, "%s=%s",
+ kv->key, kv->val);
+ }
+ }
+ if (!tmp) {
+ flb_error("[signv4] error allocating value");
+ }
+ buf = tmp;
+ }
+
+ flb_kv_release(&list);
+ flb_free(arr);
+
+ return buf;
+}
+
+/*
+ * Given an original list of kv headers with 'in_list' as the list headed,
+ * generate new entries on 'out_list' considering lower case headers key,
+ * sorted by keys and values and merged duplicates.
+ */
+void headers_sanitize(struct mk_list *in_list, struct mk_list *out_list)
+{
+ int x;
+ char *v_start;
+ char *v_end;
+ char *val;
+ struct mk_list *head;
+ struct mk_list *c_head;
+ struct mk_list *tmp;
+ struct mk_list out_tmp;
+ struct flb_kv *kv;
+ struct flb_kv *c_kv;
+ flb_sds_t t;
+
+ mk_list_init(&out_tmp);
+
+ /* Create lowercase key headers in the temporal list */
+ mk_list_foreach(head, in_list) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Sanitize value */
+ v_start = kv->val;
+ v_end = kv->val + flb_sds_len(kv->val);
+ while (*v_start == ' ' || *v_start == '\t') {
+ v_start++;
+ }
+ while (*v_end == ' ' || *v_end == '\t') {
+ v_end--;
+ }
+
+ /*
+ * The original headers might have upper case characters, for safety just
+ * make a copy of them so we can lowercase them if required.
+ */
+ kv = flb_kv_item_create_len(&out_tmp,
+ kv->key, flb_sds_len(kv->key),
+ v_start, v_end - v_start);
+ if (kv == NULL) {
+ continue;
+ }
+ for (x = 0; x < flb_sds_len(kv->key); x++) {
+ kv->key[x] = tolower(kv->key[x]);
+ }
+
+ /*
+ * trim: kv->val alreay have a copy of the original value, now we need
+ * to look for double empty spaces in the middle of the value and do
+ * proper adjustments.
+ */
+ val = kv->val;
+ while (v_start < v_end) {
+ if (*v_start == ' ') {
+ if (v_start < v_end && *(v_start + 1) == ' ') {
+ v_start++;
+ continue;
+ }
+ }
+ *val = *v_start;
+ v_start++;
+ val++;
+ }
+ *val = '\0';
+ flb_sds_len_set(kv->val, val - kv->val);
+ }
+
+ /* Find and merge duplicates */
+ mk_list_foreach_safe(head, tmp, &out_tmp) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Check if this kv exists in out_list */
+ c_kv = NULL;
+ mk_list_foreach(c_head, out_list) {
+ c_kv = mk_list_entry(c_head, struct flb_kv, _head);
+ if (strcmp(kv->key, c_kv->key) == 0) {
+ break;
+ }
+ c_kv = NULL;
+ }
+
+ /* if c_kv is set, means the key already exists in the outgoing list */
+ if (c_kv) {
+ t = flb_sds_printf(&c_kv->val, ",%s", kv->val);
+ c_kv->val = t;
+ flb_kv_item_destroy(kv);
+ }
+ else {
+ mk_list_del(&kv->_head);
+ mk_list_add(&kv->_head, out_list);
+ }
+ }
+}
+
+/*
+ * Task 1: Create a canonical request
+ * ==================================
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+ *
+ * CanonicalRequest =
+ * HTTPRequestMethod + '\n' +
+ * CanonicalURI + '\n' +
+ * CanonicalQueryString + '\n' +
+ * CanonicalHeaders + '\n' +
+ * SignedHeaders + '\n' +
+ * HexEncode(Hash(RequestPayload))
+ */
+static flb_sds_t flb_signv4_canonical_request(struct flb_http_client *c,
+ int normalize_uri,
+ int amz_date_header,
+ char *amzdate,
+ char *security_token,
+ int s3_mode,
+ struct mk_list *excluded_headers,
+ flb_sds_t *signed_headers)
+{
+ int i;
+ int len;
+ int items;
+ int all_items;
+ int excluded_items;
+ int post_params = FLB_FALSE;
+ int result;
+ size_t size;
+ int skip_header;
+ char *val;
+ struct flb_kv **arr;
+ flb_sds_t cr;
+ flb_sds_t uri;
+ flb_sds_t tmp = NULL;
+ flb_sds_t params = NULL;
+ flb_sds_t payload_hash = NULL;
+ struct flb_kv *kv;
+ struct mk_list list_tmp;
+ struct mk_list *head;
+ struct mk_list *head_2;
+ struct flb_slist_entry *sle;
+ unsigned char sha256_buf[64] = {0};
+
+ /* Size hint */
+ size = strlen(c->uri) + (mk_list_size(&c->headers) * 64) + 256;
+
+ cr = flb_sds_create_size(size);
+ if (!cr) {
+ flb_error("[signv4] cannot allocate buffer");
+ return NULL;
+ }
+
+ switch (c->method) {
+ case FLB_HTTP_GET:
+ tmp = flb_sds_cat(cr, "GET\n", 4);
+ break;
+ case FLB_HTTP_POST:
+ tmp = flb_sds_cat(cr, "POST\n", 5);
+ break;
+ case FLB_HTTP_PUT:
+ tmp = flb_sds_cat(cr, "PUT\n", 4);
+ break;
+ case FLB_HTTP_HEAD:
+ tmp = flb_sds_cat(cr, "HEAD\n", 5);
+ break;
+ };
+
+ if (!tmp) {
+ flb_error("[signv4] invalid processing of HTTP method");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ cr = tmp;
+
+ /* Our URI already contains the query string, so do the proper adjustments */
+ if (c->query_string) {
+ len = (c->query_string - c->uri) - 1;
+ }
+ else {
+ len = strlen(c->uri);
+ }
+
+ /*
+ * URI normalization is required by certain AWS service, for hence the caller
+ * plugin is responsible to enable/disable this flag. If set the URI in the
+ * canonical request will be normalized.
+ */
+ if (normalize_uri == FLB_TRUE) {
+ tmp = flb_signv4_uri_normalize_path((char *) c->uri, len);
+ if (!tmp) {
+ flb_error("[signv4] error normalizing path");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ len = flb_sds_len(tmp);
+ }
+ else {
+ tmp = (char *) c->uri;
+ }
+
+ /* Do URI encoding (rfc3986) */
+ uri = uri_encode(tmp, len);
+ if (tmp != c->uri) {
+ flb_sds_destroy(tmp);
+ }
+ if (!uri) {
+ /* error composing outgoing buffer */
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ tmp = flb_sds_cat(cr, uri, flb_sds_len(uri));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating encoded URI");
+ flb_sds_destroy(uri);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(uri);
+
+ tmp = flb_sds_cat(cr, "\n", 1);
+ if (!tmp) {
+ flb_error("[signv4] error concatenating encoded URI break line");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+
+ /* Canonical Query String */
+ tmp = NULL;
+ if (c->query_string) {
+ params = url_params_format((char *) c->query_string);
+ if (!params) {
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ tmp = flb_sds_cat(cr, params, flb_sds_len(params));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating query string");
+ flb_sds_destroy(params);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ flb_sds_destroy(params);
+ cr = tmp;
+ }
+
+ /*
+ * If the original HTTP method is POST and we have some urlencoded parameters
+ * as payload, we must handle them as we did for the query string.
+ */
+ if (c->method == FLB_HTTP_POST && c->body_len > 0) {
+ val = (char *) flb_kv_get_key_value("Content-Type", &c->headers);
+ if (val) {
+ if (strstr(val, "application/x-www-form-urlencoded")) {
+ params = url_params_format((char *) c->body_buf);
+ if (!params) {
+ flb_error("[signv4] error processing POST payload params");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ tmp = flb_sds_cat(cr, params, flb_sds_len(params));
+ if (!tmp) {
+ flb_error("[signv4] error concatenating POST payload params");
+ flb_sds_destroy(params);
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(params);
+ post_params = FLB_TRUE;
+ }
+ }
+ }
+
+ /* query string / POST separator */
+ tmp = flb_sds_cat(cr, "\n", 1);
+ if (!tmp) {
+ flb_error("[signv4] error adding params breakline separator");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ cr = tmp;
+
+ /*
+ * Calculate payload hash.
+ * This is added at the end of all canonical requests, unless
+ * S3_MODE_UNSIGNED_PAYLOAD is set.
+ * If we're using S3_MODE_SIGNED_PAYLOAD, then the hash is added to the
+ * canonical headers.
+ */
+ if (s3_mode == S3_MODE_UNSIGNED_PAYLOAD) {
+ payload_hash = flb_sds_create("UNSIGNED-PAYLOAD");
+ } else {
+ if (c->body_len > 0 && post_params == FLB_FALSE) {
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) c->body_buf, c->body_len,
+ sha256_buf, sizeof(sha256_buf));
+ }
+ else {
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) NULL, 0,
+ sha256_buf, sizeof(sha256_buf));
+ }
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ flb_error("[signv4] error hashing payload");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+
+ payload_hash = flb_sds_create_size(64);
+ if (!payload_hash) {
+ flb_error("[signv4] error formatting hashed payload");
+ flb_sds_destroy(cr);
+ return NULL;
+ }
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&payload_hash, "%02x",
+ (unsigned char) sha256_buf[i]);
+ if (!tmp) {
+ flb_error("[signv4] error formatting hashed payload");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ payload_hash = tmp;
+ }
+ }
+
+ /*
+ * Canonical Headers
+ *
+ * Add the required custom headers:
+ *
+ * - x-amz-date
+ * - x-amz-security-token (if set)
+ * - x-amz-content-sha256 (if S3_MODE_SIGNED_PAYLOAD)
+ */
+ mk_list_init(&list_tmp);
+
+ /* include x-amz-date header ? */
+ if (amz_date_header == FLB_TRUE) {
+ len = strlen(amzdate);
+ flb_http_add_header(c, "x-amz-date", 10, amzdate, len);
+ }
+
+ /* x-amz-security_token */
+ if (security_token) {
+ len = strlen(security_token);
+ flb_http_add_header(c, "x-amz-security-token", 20, security_token, len);
+ }
+
+ if (s3_mode == S3_MODE_SIGNED_PAYLOAD) {
+ flb_http_add_header(c, "x-amz-content-sha256", 20, payload_hash, 64);
+ }
+
+ headers_sanitize(&c->headers, &list_tmp);
+
+ /*
+ * For every header registered, append it to the temporal array so we can sort them
+ * later.
+ */
+ all_items = mk_list_size(&list_tmp);
+ excluded_items = 0;
+ size = (sizeof(struct flb_kv *) * (all_items));
+ arr = flb_calloc(1, size);
+ if (!arr) {
+ flb_errno();
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+
+ /* Compose temporal array to sort headers */
+ i = 0;
+ mk_list_foreach(head, &list_tmp) {
+ kv = mk_list_entry(head, struct flb_kv, _head);
+
+ /* Skip excluded headers */
+ if (excluded_headers) {
+ skip_header = FLB_FALSE;
+ mk_list_foreach(head_2, excluded_headers) {
+ sle = mk_list_entry(head_2, struct flb_slist_entry, _head);
+ if (flb_sds_casecmp(kv->key, sle->str, flb_sds_len(sle->str)) == 0) {
+
+ /* Skip header */
+ excluded_items++;
+ skip_header = FLB_TRUE;
+ break;
+ }
+ }
+ if (skip_header) {
+ continue;
+ }
+ }
+
+ arr[i] = kv;
+ i++;
+ }
+
+ /* Count items */
+ items = all_items - excluded_items;
+
+ /* Sort the headers from the temporal array */
+ qsort(arr, items, sizeof(struct flb_kv *), kv_key_cmp);
+
+ /* Iterate sorted headers and append them to the outgoing buffer */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+ tmp = flb_sds_printf(&cr, "%s:%s\n", kv->key, kv->val);
+ if (!tmp) {
+ flb_error("[signv4] error composing canonical headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ }
+
+ /* Add required breakline */
+ tmp = flb_sds_printf(&cr, "\n");
+ if (!tmp) {
+ flb_error("[signv4] error adding extra breakline separator");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+
+ /* Signed Headers for canonical request context */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+
+ /* Check if this is the last header, if so add breakline separator */
+ if (i + 1 == items) {
+ tmp = flb_sds_printf(&cr, "%s\n", kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(&cr, "%s;", kv->key);
+ }
+ if (!tmp) {
+ flb_error("[signv4] error composing canonical signed headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ }
+
+ /* Signed Headers for authorization header (Task 4) */
+ for (i = 0; i < items; i++) {
+ kv = (struct flb_kv *) arr[i];
+
+ /* Check if this is the last header, if so add breakline separator */
+ if (i + 1 == items) {
+ tmp = flb_sds_printf(signed_headers, "%s", kv->key);
+ }
+ else {
+ tmp = flb_sds_printf(signed_headers, "%s;", kv->key);
+ }
+ if (!tmp) {
+ flb_error("[signv4] error composing auth signed headers");
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ *signed_headers = tmp;
+ }
+
+ flb_free(arr);
+ flb_kv_release(&list_tmp);
+
+ /* Add Payload Hash */
+ tmp = flb_sds_printf(&cr, "%s", payload_hash);
+ if (!tmp) {
+ flb_error("[signv4] error adding payload hash");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(payload_hash);
+ return NULL;
+ }
+ cr = tmp;
+ flb_sds_destroy(payload_hash);
+
+ return cr;
+}
+
+/*
+ * Task 2
+ * ======
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
+ */
+static flb_sds_t flb_signv4_string_to_sign(struct flb_http_client *c,
+ flb_sds_t cr, char *amzdate,
+ char *datestamp, char *service,
+ char *region)
+{
+ int i;
+ int result;
+ flb_sds_t tmp;
+ flb_sds_t sign;
+ unsigned char sha256_buf[64] = {0};
+
+ sign = flb_sds_create_size(256);
+ if (!sign) {
+ flb_error("[signv4] cannot create buffer for signature");
+ return NULL;
+ }
+
+ /* Hashing Algorithm */
+ tmp = flb_sds_cat(sign, "AWS4-HMAC-SHA256\n", 17);
+ if (!tmp) {
+ flb_error("[signv4] cannot add algorithm to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+
+ /* Amazon date */
+ tmp = flb_sds_printf(&sign, "%s\n", amzdate);
+ if (!tmp) {
+ flb_error("[signv4] cannot add amz-date to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+
+ /* Credentials Scope */
+ tmp = flb_sds_printf(&sign, "%s/%s/%s/aws4_request\n",
+ datestamp, region, service);
+ if (!tmp) {
+ flb_error("[signv4] cannot add credentials scope to signature");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+
+ /* Hash of Canonical Request */
+ result = flb_hash_simple(FLB_HASH_SHA256,
+ (unsigned char *) cr, flb_sds_len(cr),
+ sha256_buf, sizeof(sha256_buf));
+
+ if (result != FLB_CRYPTO_SUCCESS) {
+ flb_error("[signv4] error hashing canonical request");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+
+ for (i = 0; i < 32; i++) {
+ tmp = flb_sds_printf(&sign, "%02x", (unsigned char) sha256_buf[i]);
+ if (!tmp) {
+ flb_error("[signv4] error formatting hashed canonical request");
+ flb_sds_destroy(sign);
+ return NULL;
+ }
+ sign = tmp;
+ }
+
+ return sign;
+}
+
+/*
+ * Task 3
+ * ======
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
+ *
+ * TODO: The signing key could be cached to improve performance
+ */
+static flb_sds_t flb_signv4_calculate_signature(flb_sds_t string_to_sign,
+ char *datestamp, char *service,
+ char *region, char *secret_key)
+{
+ int len;
+ int klen = 32;
+ flb_sds_t tmp;
+ flb_sds_t key;
+ unsigned char key_date[32];
+ unsigned char key_region[32];
+ unsigned char key_service[32];
+ unsigned char key_signing[32];
+ unsigned char signature[32];
+
+ /* Compose initial key */
+ key = flb_sds_create_size(256);
+ if (!key) {
+ flb_error("[signv4] cannot create buffer for signature calculation");
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&key, "AWS4%s", secret_key);
+ if (!tmp) {
+ flb_error("[signv4] error formatting initial key");
+ flb_sds_destroy(key);
+ return NULL;
+ }
+ key = tmp;
+
+ /* key_date */
+ len = strlen(datestamp);
+ hmac_sha256_sign(key_date, (unsigned char *) key, flb_sds_len(key),
+ (unsigned char *) datestamp, len);
+ flb_sds_destroy(key);
+
+ /* key_region */
+ len = strlen(region);
+ hmac_sha256_sign(key_region, key_date, klen, (unsigned char *) region, len);
+
+ /* key_service */
+ len = strlen(service);
+ hmac_sha256_sign(key_service, key_region, klen, (unsigned char *) service, len);
+
+ /* key_signing */
+ hmac_sha256_sign(key_signing, key_service, klen,
+ (unsigned char *) "aws4_request", 12);
+
+ /* Signature */
+ hmac_sha256_sign(signature, key_signing, klen,
+ (unsigned char *) string_to_sign, flb_sds_len(string_to_sign));
+
+ return sha256_to_hex(signature);
+}
+
+/*
+ * Task 4
+ * ======
+ *
+ * https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
+ */
+static flb_sds_t flb_signv4_add_authorization(struct flb_http_client *c,
+ char *access_key,
+ char *datestamp,
+ char *region, char *service,
+ flb_sds_t signed_headers,
+ flb_sds_t signature)
+{
+ int ret;
+ int len;
+ flb_sds_t tmp;
+ flb_sds_t header_value;
+
+ header_value = flb_sds_create_size(512);
+ if (!header_value) {
+ flb_error("[signv4] cannot allocate buffer for authorization header");
+ return NULL;
+ }
+
+ tmp = flb_sds_printf(&header_value,
+ "AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, "
+ "SignedHeaders=%s, Signature=%s",
+ access_key, datestamp, region, service,
+ signed_headers, signature);
+ if (!tmp) {
+ flb_error("[signv4] error composing authorization header");
+ flb_sds_destroy(header_value);
+ return NULL;
+ }
+ header_value = tmp;
+
+ len = flb_sds_len(header_value);
+ ret = flb_http_add_header(c, "Authorization", 13, header_value, len);
+ if (ret == -1) {
+ flb_error("[signv4] could not add authorization header");
+ flb_sds_destroy(header_value);
+ return NULL;
+
+ }
+
+ /* Return the composed final header for testing if required */
+ return header_value;
+}
+
+flb_sds_t flb_signv4_do(struct flb_http_client *c, int normalize_uri,
+ int amz_date_header,
+ time_t t_now,
+ char *region, char *service,
+ int s3_mode,
+ struct mk_list *unsigned_headers,
+ struct flb_aws_provider *provider)
+{
+ char amzdate[32];
+ char datestamp[32];
+ struct tm *gmt;
+ flb_sds_t cr;
+ flb_sds_t string_to_sign;
+ flb_sds_t signature;
+ flb_sds_t signed_headers;
+ flb_sds_t auth_header;
+ struct flb_aws_credentials *creds;
+
+ creds = provider->provider_vtable->get_credentials(provider);
+ if (!creds) {
+ flb_error("[signv4] Provider returned no credentials, service=%s",
+ service);
+ return NULL;
+ }
+
+ gmt = flb_calloc(1, sizeof(struct tm));
+ if (!gmt) {
+ flb_errno();
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ if (!gmtime_r(&t_now, gmt)) {
+ flb_error("[signv4] error converting given unix timestamp");
+ flb_free(gmt);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ strftime(amzdate, sizeof(amzdate) - 1, "%Y%m%dT%H%M%SZ", gmt);
+ strftime(datestamp, sizeof(datestamp) - 1, "%Y%m%d", gmt);
+ flb_free(gmt);
+
+ /* Task 1: canonical request */
+ signed_headers = flb_sds_create_size(256);
+ if (!signed_headers) {
+ flb_error("[signedv4] cannot allocate buffer for auth signed headers");
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ cr = flb_signv4_canonical_request(c, normalize_uri,
+ amz_date_header, amzdate,
+ creds->session_token, s3_mode,
+ unsigned_headers,
+ &signed_headers);
+ if (!cr) {
+ flb_error("[signv4] failed canonical request");
+ flb_sds_destroy(signed_headers);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+
+ /* Task 2: string to sign */
+ string_to_sign = flb_signv4_string_to_sign(c, cr, amzdate,
+ datestamp, service, region);
+ if (!string_to_sign) {
+ flb_error("[signv4] failed string to sign");
+ flb_sds_destroy(cr);
+ flb_sds_destroy(signed_headers);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+ flb_sds_destroy(cr);
+
+ /* Task 3: calculate the signature */
+ signature = flb_signv4_calculate_signature(string_to_sign, datestamp,
+ service, region,
+ creds->secret_access_key);
+ if (!signature) {
+ flb_error("[signv4] failed calculate_string");
+ flb_sds_destroy(signed_headers);
+ flb_sds_destroy(string_to_sign);
+ flb_aws_credentials_destroy(creds);
+ return NULL;
+ }
+ flb_sds_destroy(string_to_sign);
+
+ /* Task 4: add signature to HTTP request */
+ auth_header = flb_signv4_add_authorization(c,
+ creds->access_key_id,
+ datestamp, region, service,
+ signed_headers, signature);
+ flb_sds_destroy(signed_headers);
+ flb_sds_destroy(signature);
+ flb_aws_credentials_destroy(creds);
+
+ if (!auth_header) {
+ flb_error("[signv4] error creating authorization header");
+ return NULL;
+ }
+
+ return auth_header;
+}