1 /* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
2 * Copyright 2020 MariaDB Corporation Ab. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA
18 */
19
20 #include "config.h"
21 #include "common.h"
22 #include "sha256.h"
23
24 #include <math.h>
25
26 const char *default_iam_domain = "iam.amazonaws.com";
27 const char *default_sts_domain = "sts.amazonaws.com";
28 const char *iam_request_region = "us-east-1";
29
set_error(ms3_st * ms3,const char * error)30 static void set_error(ms3_st *ms3, const char *error)
31 {
32 ms3_cfree(ms3->last_error);
33
34 if (!error)
35 {
36 ms3->last_error = NULL;
37 return;
38 }
39
40 ms3->last_error = ms3_cstrdup(error);
41 }
42
set_error_nocopy(ms3_st * ms3,char * error)43 static void set_error_nocopy(ms3_st *ms3, char *error)
44 {
45 ms3_cfree(ms3->last_error);
46
47 if (!error)
48 {
49 ms3->last_error = NULL;
50 return;
51 }
52
53 ms3->last_error = error;
54 }
55
header_callback(char * buffer,size_t size,size_t nitems,void * userdata)56 static size_t header_callback(char *buffer, size_t size,
57 size_t nitems, void *userdata)
58 {
59 ms3debug("%.*s\n", (int)(nitems * size), buffer);
60
61 if (userdata)
62 {
63 // HEAD request
64 if (!strncasecmp(buffer, "Last-Modified", 13))
65 {
66 ms3_status_st *status = (ms3_status_st *) userdata;
67 // Date/time, format: Fri, 15 Mar 2019 16:58:54 GMT
68 struct tm ttmp = {0};
69 strptime(buffer + 15, "%a, %d %b %Y %H:%M:%S %Z", &ttmp);
70 status->created = mktime(&ttmp);
71 }
72 else if (!strncasecmp(buffer, "Content-Length", 14))
73 {
74 ms3_status_st *status = (ms3_status_st *) userdata;
75 // Length
76 status->length = strtoull(buffer + 16, NULL, 10);
77 }
78 }
79
80 return nitems * size;
81 }
82
body_callback(void * buffer,size_t size,size_t nitems,void * userdata)83 static size_t body_callback(void *buffer, size_t size,
84 size_t nitems, void *userdata)
85 {
86 uint8_t *ptr;
87 size_t realsize = nitems * size;
88
89 struct memory_buffer_st *mem = (struct memory_buffer_st *)userdata;
90
91 if (realsize + mem->length >= mem->alloced)
92 {
93 size_t additional_size = mem->buffer_chunk_size;
94
95 if (realsize >= mem->buffer_chunk_size)
96 {
97 additional_size = (ceil((double)realsize / (double)mem->buffer_chunk_size) + 1)
98 * mem->buffer_chunk_size;
99 }
100
101 ptr = (uint8_t *)ms3_crealloc(mem->data, mem->alloced + additional_size);
102
103 if (!ptr)
104 {
105 ms3debug("Curl response OOM");
106 return 0;
107 }
108
109 mem->alloced += additional_size;
110 mem->data = ptr;
111 }
112
113 memcpy(&(mem->data[mem->length]), buffer, realsize);
114 mem->length += realsize;
115 mem->data[mem->length] = '\0';
116
117 ms3debug("Read %zu bytes, buffer %zu bytes", realsize, mem->length);
118 // ms3debug("Data: %s", (char*)buffer);
119 return nitems * size;
120 }
121
build_assume_role_request_uri(CURL * curl,const char * base_domain,const char * query,bool use_http)122 static uint8_t build_assume_role_request_uri(CURL *curl, const char *base_domain, const char *query, bool use_http)
123 {
124 char uri_buffer[MAX_URI_LENGTH];
125 const char *domain;
126 const uint8_t path_parts = 10; // "https://" + "." + "/"
127 const char *http_protocol = "http";
128 const char *https_protocol = "https";
129 const char *protocol;
130
131 if (base_domain)
132 {
133 domain = base_domain;
134 }
135 else
136 {
137 domain = default_sts_domain;
138 }
139
140 if (use_http)
141 {
142 protocol = http_protocol;
143 }
144 else
145 {
146 protocol = https_protocol;
147 }
148
149 if (query)
150 {
151 if (path_parts + strlen(domain) + strlen(query) >= MAX_URI_LENGTH - 1)
152 {
153 return MS3_ERR_URI_TOO_LONG;
154 }
155
156 snprintf(uri_buffer, MAX_URI_LENGTH - 1, "%s://%s/?%s", protocol,
157 domain, query);
158 }
159 else
160 {
161 return MS3_ERR_PARAMETER;
162 }
163
164 ms3debug("URI: %s", uri_buffer);
165 curl_easy_setopt(curl, CURLOPT_URL, uri_buffer);
166 return 0;
167 }
168
generate_assume_role_query(CURL * curl,const char * action,size_t role_duration,const char * version,const char * role_session_name,const char * role_arn,const char * continuation,char * query_buffer)169 static char *generate_assume_role_query(CURL *curl, const char *action, size_t role_duration,
170 const char *version, const char *role_session_name, const char *role_arn,
171 const char *continuation, char *query_buffer)
172 {
173 size_t query_buffer_length = 0;
174 char *encoded;
175 query_buffer[0] = '\0';
176
177 if (action)
178 {
179 encoded = curl_easy_escape(curl, action, (int)strlen(action));
180 query_buffer_length = strlen(query_buffer);
181 if (query_buffer_length)
182 {
183 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
184 "&Action=%s", encoded);
185 }
186 else
187 {
188 snprintf(query_buffer, 3072, "Action=%s", encoded);
189 }
190 curl_free(encoded);
191 }
192 if (role_duration >= 900 && role_duration <= 43200)
193 {
194 query_buffer_length = strlen(query_buffer);
195 if (query_buffer_length)
196 {
197 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
198 "&DurationSeconds=%zu", role_duration);
199 }
200 else
201 {
202 snprintf(query_buffer, 3072, "DurationSeconds=%zu", role_duration);
203 }
204 }
205 if (continuation)
206 {
207 encoded = curl_easy_escape(curl, continuation, (int)strlen(continuation));
208 query_buffer_length = strlen(query_buffer);
209 if (query_buffer_length)
210 {
211 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
212 "&Marker=%s", encoded);
213 }
214 else
215 {
216 snprintf(query_buffer, 3072, "Marker=%s", encoded);
217 }
218 curl_free(encoded);
219 }
220 if (role_arn)
221 {
222 encoded = curl_easy_escape(curl, role_arn, (int)strlen(role_arn));
223 query_buffer_length = strlen(query_buffer);
224 if (query_buffer_length)
225 {
226 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
227 "&RoleArn=%s", encoded);
228 }
229 else
230 {
231 snprintf(query_buffer, 3072, "RoleArn=%s", encoded);
232 }
233 curl_free(encoded);
234 }
235 if (role_session_name)
236 {
237 encoded = curl_easy_escape(curl, role_session_name, (int)strlen(role_session_name));
238 query_buffer_length = strlen(query_buffer);
239 if (query_buffer_length)
240 {
241 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
242 "&RoleSessionName=%s", encoded);
243 }
244 else
245 {
246 snprintf(query_buffer, 3072, "RoleSessionName=%s", encoded);
247 }
248 curl_free(encoded);
249 }
250 if (version)
251 {
252 encoded = curl_easy_escape(curl, version, (int)strlen(version));
253 query_buffer_length = strlen(query_buffer);
254 if (query_buffer_length)
255 {
256 snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
257 "&Version=%s", encoded);
258 }
259 else
260 {
261 snprintf(query_buffer, 3072, "Version=%s", encoded);
262 }
263 curl_free(encoded);
264 }
265
266 return query_buffer;
267 }
268
269
generate_assume_role_request_hash(uri_method_t method,const char * query,char * post_hash,struct curl_slist * headers,char * return_hash)270 static uint8_t generate_assume_role_request_hash(uri_method_t method, const char *query, char *post_hash,
271 struct curl_slist *headers, char *return_hash)
272 {
273 char signing_data[3072];
274 size_t pos = 0;
275 uint8_t sha256hash[32]; // SHA_256 binary length
276 uint8_t hash_pos = 0;
277 uint8_t i;
278 struct curl_slist *current_header = headers;
279
280 // Method first
281 switch (method)
282 {
283 case MS3_GET:
284 {
285 sprintf(signing_data, "GET\n");
286 pos += 4;
287 break;
288 }
289
290 case MS3_HEAD:
291 {
292 sprintf(signing_data, "HEAD\n");
293 pos += 5;
294 break;
295 }
296
297 case MS3_PUT:
298 {
299 sprintf(signing_data, "PUT\n");
300 pos += 4;
301 break;
302 }
303
304 case MS3_DELETE:
305 {
306 sprintf(signing_data, "DELETE\n");
307 pos += 7;
308 break;
309 }
310
311 default:
312 {
313 ms3debug("Bad method detected");
314 return MS3_ERR_IMPOSSIBLE;
315 }
316 }
317
318 // URL query (if exists)
319 if (query)
320 {
321 snprintf(signing_data + pos, sizeof(signing_data) - pos, "/\n%s\n", query);
322 pos += strlen(query) + 3;
323 }
324 else
325 {
326 sprintf(signing_data + pos, "\n");
327 pos++;
328 }
329
330 do
331 {
332 snprintf(signing_data + pos, sizeof(signing_data) - pos, "%s\n",
333 current_header->data);
334 pos += strlen(current_header->data) + 1;
335 }
336 while ((current_header = current_header->next));
337
338 // List if header names
339 // The newline between headers and this is important
340 snprintf(signing_data + pos, sizeof(signing_data) - pos,
341 "\nhost;x-amz-content-sha256;x-amz-date\n");
342 pos += 38;
343
344 // Hash of post data (can be hash of empty)
345 snprintf(signing_data + pos, sizeof(signing_data) - pos, "%.*s", 64, post_hash);
346 //pos+= 64;
347
348 // Hash all of the above
349 sha256((uint8_t *)signing_data, strlen(signing_data), (uint8_t *)sha256hash);
350
351 for (i = 0; i < 32; i++)
352 {
353 sprintf(return_hash + hash_pos, "%.2x", sha256hash[i]);
354 hash_pos += 2;
355 }
356
357 ms3debug("Signature data: %s", signing_data);
358 ms3debug("Signature: %.*s", 64, return_hash);
359
360 return 0;
361 }
362
363 static uint8_t
build_assume_role_request_headers(CURL * curl,struct curl_slist ** head,const char * base_domain,const char * endpoint_type,const char * region,const char * key,const char * secret,const char * query,uri_method_t method,struct put_buffer_st * post_data)364 build_assume_role_request_headers(CURL *curl, struct curl_slist **head,
365 const char *base_domain,
366 const char* endpoint_type,
367 const char *region, const char *key,
368 const char *secret, const char *query,
369 uri_method_t method,
370 struct put_buffer_st *post_data)
371 {
372 uint8_t ret = 0;
373 time_t now;
374 struct tm tmp_tm;
375 char headerbuf[3072];
376 char secrethead[45];
377 char date[9];
378 char sha256hash[65];
379 char post_hash[65];
380 uint8_t tmp_hash[32];
381 // Alternate between these two so hmac doesn't overwrite itself
382 uint8_t hmac_hash[32];
383 uint8_t hmac_hash2[32];
384 uint8_t hash_pos = 0;
385 const char *domain;
386 const char *type;
387 struct curl_slist *headers = NULL;
388 uint8_t offset;
389 uint8_t i;
390 struct curl_slist *current_header;
391
392 // Host header
393 if (base_domain)
394 {
395 domain = base_domain;
396 }
397 else
398 {
399 domain = default_sts_domain;
400 }
401
402 if (endpoint_type)
403 {
404 type = endpoint_type;
405 }
406 else
407 {
408 type = "sts";
409 }
410
411 snprintf(headerbuf, sizeof(headerbuf), "host:%s", domain);
412
413 headers = curl_slist_append(headers, headerbuf);
414 *head = headers;
415
416 // Hash post data
417 sha256(post_data->data, post_data->length, tmp_hash);
418
419 for (i = 0; i < 32; i++)
420 {
421 sprintf(post_hash + hash_pos, "%.2x", tmp_hash[i]);
422 hash_pos += 2;
423 }
424
425 snprintf(headerbuf, sizeof(headerbuf), "x-amz-content-sha256:%.*s", 64,
426 post_hash);
427 headers = curl_slist_append(headers, headerbuf);
428
429 // Date/time header
430 time(&now);
431 snprintf(headerbuf, sizeof(headerbuf), "x-amz-date:");
432 offset = strlen(headerbuf);
433 gmtime_r(&now, &tmp_tm);
434 strftime(headerbuf + offset, sizeof(headerbuf) - offset, "%Y%m%dT%H%M%SZ",
435 &tmp_tm);
436 headers = curl_slist_append(headers, headerbuf);
437
438 // Builds the request hash
439 ret = generate_assume_role_request_hash(method, query, post_hash, headers, sha256hash);
440
441 if (ret)
442 {
443 return ret;
444 }
445
446 // User signing key hash
447 // Date hashed using AWS4:secret_key
448 snprintf(secrethead, sizeof(secrethead), "AWS4%.*s", 40, secret);
449 strftime(headerbuf, sizeof(headerbuf), "%Y%m%d", &tmp_tm);
450 hmac_sha256((uint8_t *)secrethead, strlen(secrethead), (uint8_t *)headerbuf,
451 strlen(headerbuf), hmac_hash);
452
453 // Region signed by above key
454 hmac_sha256(hmac_hash, 32, (uint8_t *)region, strlen(region),
455 hmac_hash2);
456
457 // Service signed by above key
458 hmac_sha256(hmac_hash2, 32, (uint8_t *)type, strlen(type),
459 hmac_hash);
460
461 // Request version signed by above key (always "aws4_request")
462 sprintf(headerbuf, "aws4_request");
463 hmac_sha256(hmac_hash, 32, (uint8_t *)headerbuf, strlen(headerbuf),
464 hmac_hash2);
465
466 // Sign everything with the key
467 snprintf(headerbuf, sizeof(headerbuf), "AWS4-HMAC-SHA256\n");
468 offset = strlen(headerbuf);
469 strftime(headerbuf + offset, sizeof(headerbuf) - offset, "%Y%m%dT%H%M%SZ\n",
470 &tmp_tm);
471 offset = strlen(headerbuf);
472 strftime(date, 9, "%Y%m%d", &tmp_tm);
473 snprintf(headerbuf + offset, sizeof(headerbuf) - offset,
474 "%.*s/%s/%s/aws4_request\n%.*s", 8, date, region, type, 64, sha256hash);
475 ms3debug("Data to sign: %s", headerbuf);
476 hmac_sha256(hmac_hash2, 32, (uint8_t *)headerbuf, strlen(headerbuf),
477 hmac_hash);
478
479 hash_pos = 0;
480
481 for (i = 0; i < 32; i++)
482 {
483 sprintf(sha256hash + hash_pos, "%.2x", hmac_hash[i]);
484 hash_pos += 2;
485 }
486
487 // Make auth header
488 snprintf(headerbuf, sizeof(headerbuf),
489 "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=%s",
490 key, date, region, type, sha256hash);
491
492 headers = curl_slist_append(headers, headerbuf);
493
494 // Disable this header or PUT will barf with a 501
495 sprintf(headerbuf, "Transfer-Encoding:");
496 headers = curl_slist_append(headers, headerbuf);
497
498 current_header = headers;
499
500 do
501 {
502 ms3debug("Header: %s", current_header->data);
503 }
504 while ((current_header = current_header->next));
505
506 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
507
508 return 0;
509 }
510
execute_assume_role_request(ms3_st * ms3,command_t cmd,const uint8_t * data,size_t data_size,char * continuation)511 uint8_t execute_assume_role_request(ms3_st *ms3, command_t cmd,
512 const uint8_t *data, size_t data_size,
513 char *continuation)
514 {
515 CURL *curl = NULL;
516 struct curl_slist *headers = NULL;
517 uint8_t res = 0;
518 struct memory_buffer_st mem;
519 uri_method_t method;
520 char *query = NULL;
521 struct put_buffer_st post_data;
522 CURLcode curl_res;
523 long response_code = 0;
524 char* endpoint = NULL;
525 const char* region = iam_request_region;
526 char endpoint_type[8];
527
528 mem.data = NULL;
529 mem.length = 0;
530 mem.alloced = 1;
531 mem.buffer_chunk_size = ms3->buffer_chunk_size;
532
533 post_data.data = (uint8_t *) data;
534 post_data.length = data_size;
535 post_data.offset = 0;
536
537 curl = ms3->curl;
538
539 if (!ms3->first_run)
540 {
541 curl_easy_reset(curl);
542 }
543 else
544 {
545 ms3->first_run = false;
546 }
547
548 if (cmd == MS3_CMD_ASSUME_ROLE)
549 {
550 query = generate_assume_role_query(curl, "AssumeRole", ms3->role_session_duration, "2011-06-15", "libmariaS3",
551 ms3->iam_role_arn, continuation, ms3->query_buffer);
552 endpoint = ms3->sts_endpoint;
553 region = ms3->sts_region;
554 sprintf(endpoint_type, "sts");
555 method = MS3_GET;
556 }
557 else if (cmd == MS3_CMD_LIST_ROLE)
558 {
559 query = generate_assume_role_query(curl, "ListRoles", 0, "2010-05-08", NULL, NULL, continuation, ms3->query_buffer);
560 endpoint = ms3->iam_endpoint;
561 sprintf(endpoint_type, "iam");
562 method = MS3_GET;
563 }
564
565 res = build_assume_role_request_uri(curl, endpoint, query, ms3->use_http);
566
567 if (res)
568 {
569 return res;
570 }
571
572 res = build_assume_role_request_headers(curl, &headers, endpoint,
573 endpoint_type, region,
574 ms3->s3key, ms3->s3secret, query,
575 method, &post_data);
576
577 if (res)
578 {
579 ms3_cfree(mem.data);
580 curl_slist_free_all(headers);
581
582 return res;
583 }
584
585 if (ms3->disable_verification)
586 {
587 ms3debug("Disabling SSL verification");
588 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
589 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
590 }
591
592 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
593 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_callback);
594 curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&mem);
595 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
596 curl_res = curl_easy_perform(curl);
597
598 if (curl_res != CURLE_OK)
599 {
600 ms3debug("Curl error: %s", curl_easy_strerror(curl_res));
601 set_error(ms3, curl_easy_strerror(curl_res));
602 ms3_cfree(mem.data);
603 curl_slist_free_all(headers);
604
605 return MS3_ERR_REQUEST_ERROR;
606 }
607
608 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
609 ms3debug("Response code: %ld", response_code);
610
611 if (response_code == 404)
612 {
613 char *message = parse_error_message((char *)mem.data, mem.length);
614
615 if (message)
616 {
617 ms3debug("Response message: %s", message);
618 }
619
620 set_error_nocopy(ms3, message);
621 res = MS3_ERR_NOT_FOUND;
622 }
623 else if (response_code == 403)
624 {
625 char *message = parse_error_message((char *)mem.data, mem.length);
626
627 if (message)
628 {
629 ms3debug("Response message: %s", message);
630 }
631
632 set_error_nocopy(ms3, message);
633 res = MS3_ERR_AUTH;
634 }
635 else if (response_code >= 400)
636 {
637 char *message = parse_error_message((char *)mem.data, mem.length);
638
639 if (message)
640 {
641 ms3debug("Response message: %s", message);
642 }
643
644 set_error_nocopy(ms3, message);
645 res = MS3_ERR_SERVER;
646 }
647
648 switch (cmd)
649 {
650 case MS3_CMD_LIST_ROLE:
651 {
652 char *cont = NULL;
653 res = parse_role_list_response((const char *)mem.data, mem.length, ms3->iam_role ,ms3->iam_role_arn, &cont);
654
655 if (cont && res)
656 {
657 res = execute_assume_role_request(ms3, cmd, data, data_size, cont);
658 if (res)
659 {
660 ms3_cfree(cont);
661 ms3_cfree(mem.data);
662 curl_slist_free_all(headers);
663 return res;
664 }
665 ms3_cfree(cont);
666 }
667
668 ms3_cfree(mem.data);
669 break;
670 }
671
672 case MS3_CMD_ASSUME_ROLE:
673 {
674 if (res)
675 {
676 ms3_cfree(mem.data);
677 curl_slist_free_all(headers);
678 return res;
679 }
680 res = parse_assume_role_response((const char *)mem.data, mem.length, ms3->role_key, ms3->role_secret, ms3->role_session_token);
681 ms3_cfree(mem.data);
682 break;
683 }
684
685 case MS3_CMD_LIST:
686 case MS3_CMD_LIST_RECURSIVE:
687 case MS3_CMD_PUT:
688 case MS3_CMD_GET:
689 case MS3_CMD_DELETE:
690 case MS3_CMD_HEAD:
691 case MS3_CMD_COPY:
692 default:
693 {
694 ms3_cfree(mem.data);
695 ms3debug("Bad cmd detected");
696 res = MS3_ERR_IMPOSSIBLE;
697 }
698 }
699
700 curl_slist_free_all(headers);
701
702 return res;
703 }
704