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