• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 #include "curl_setup.h"
26 
27 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
28 
29 #include "urldata.h"
30 #include "strcase.h"
31 #include "strdup.h"
32 #include "http_aws_sigv4.h"
33 #include "curl_sha256.h"
34 #include "transfer.h"
35 #include "parsedate.h"
36 #include "sendf.h"
37 
38 #include <time.h>
39 
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
43 #include "memdebug.h"
44 
45 #include "slist.h"
46 
47 #define HMAC_SHA256(k, kl, d, dl, o)        \
48   do {                                      \
49     ret = Curl_hmacit(Curl_HMAC_SHA256,     \
50                       (unsigned char *)k,   \
51                       kl,                   \
52                       (unsigned char *)d,   \
53                       dl, o);               \
54     if(ret) {                               \
55       goto fail;                            \
56     }                                       \
57   } while(0)
58 
59 #define TIMESTAMP_SIZE 17
60 
61 /* hex-encoded with trailing null */
62 #define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1)
63 
sha256_to_hex(char * dst,unsigned char * sha)64 static void sha256_to_hex(char *dst, unsigned char *sha)
65 {
66   int i;
67 
68   for(i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
69     msnprintf(dst + (i * 2), SHA256_HEX_LENGTH - (i * 2), "%02x", sha[i]);
70   }
71 }
72 
find_date_hdr(struct Curl_easy * data,const char * sig_hdr)73 static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
74 {
75   char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
76 
77   if(tmp)
78     return tmp;
79   return Curl_checkheaders(data, STRCONST("Date"));
80 }
81 
82 /* remove whitespace, and lowercase all headers */
trim_headers(struct curl_slist * head)83 static void trim_headers(struct curl_slist *head)
84 {
85   struct curl_slist *l;
86   for(l = head; l; l = l->next) {
87     char *value; /* to read from */
88     char *store;
89     size_t colon = strcspn(l->data, ":");
90     Curl_strntolower(l->data, l->data, colon);
91 
92     value = &l->data[colon];
93     if(!*value)
94       continue;
95     ++value;
96     store = value;
97 
98     /* skip leading whitespace */
99     while(*value && ISBLANK(*value))
100       value++;
101 
102     while(*value) {
103       int space = 0;
104       while(*value && ISBLANK(*value)) {
105         value++;
106         space++;
107       }
108       if(space) {
109         /* replace any number of consecutive whitespace with a single space,
110            unless at the end of the string, then nothing */
111         if(*value)
112           *store++ = ' ';
113       }
114       else
115         *store++ = *value++;
116     }
117     *store = 0; /* null terminate */
118   }
119 }
120 
121 /* maximum length for the aws sivg4 parts */
122 #define MAX_SIGV4_LEN 64
123 #define MAX_SIGV4_LEN_TXT "64"
124 
125 #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
126 
127 #define MAX_HOST_LEN 255
128 /* FQDN + host: */
129 #define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:"))
130 
131 /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
132 #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
133 
134 /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
make_headers(struct Curl_easy * data,const char * hostname,char * timestamp,char * provider1,char ** date_header,char * content_sha256_header,struct dynbuf * canonical_headers,struct dynbuf * signed_headers)135 static CURLcode make_headers(struct Curl_easy *data,
136                              const char *hostname,
137                              char *timestamp,
138                              char *provider1,
139                              char **date_header,
140                              char *content_sha256_header,
141                              struct dynbuf *canonical_headers,
142                              struct dynbuf *signed_headers)
143 {
144   char date_hdr_key[DATE_HDR_KEY_LEN];
145   char date_full_hdr[DATE_FULL_HDR_LEN];
146   struct curl_slist *head = NULL;
147   struct curl_slist *tmp_head = NULL;
148   CURLcode ret = CURLE_OUT_OF_MEMORY;
149   struct curl_slist *l;
150   int again = 1;
151 
152   /* provider1 mid */
153   Curl_strntolower(provider1, provider1, strlen(provider1));
154   provider1[0] = Curl_raw_toupper(provider1[0]);
155 
156   msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1);
157 
158   /* provider1 lowercase */
159   Curl_strntolower(provider1, provider1, 1); /* first byte only */
160   msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
161             "x-%s-date:%s", provider1, timestamp);
162 
163   if(Curl_checkheaders(data, STRCONST("Host"))) {
164     head = NULL;
165   }
166   else {
167     char full_host[FULL_HOST_LEN + 1];
168 
169     if(data->state.aptr.host) {
170       size_t pos;
171 
172       if(strlen(data->state.aptr.host) > FULL_HOST_LEN) {
173         ret = CURLE_URL_MALFORMAT;
174         goto fail;
175       }
176       strcpy(full_host, data->state.aptr.host);
177       /* remove /r/n as the separator for canonical request must be '\n' */
178       pos = strcspn(full_host, "\n\r");
179       full_host[pos] = 0;
180     }
181     else {
182       if(strlen(hostname) > MAX_HOST_LEN) {
183         ret = CURLE_URL_MALFORMAT;
184         goto fail;
185       }
186       msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname);
187     }
188 
189     head = curl_slist_append(NULL, full_host);
190     if(!head)
191       goto fail;
192   }
193 
194 
195   if (*content_sha256_header) {
196     tmp_head = curl_slist_append(head, content_sha256_header);
197     if(!tmp_head)
198       goto fail;
199     head = tmp_head;
200   }
201 
202   for(l = data->set.headers; l; l = l->next) {
203     tmp_head = curl_slist_append(head, l->data);
204     if(!tmp_head)
205       goto fail;
206     head = tmp_head;
207   }
208 
209   trim_headers(head);
210 
211   *date_header = find_date_hdr(data, date_hdr_key);
212   if(!*date_header) {
213     tmp_head = curl_slist_append(head, date_full_hdr);
214     if(!tmp_head)
215       goto fail;
216     head = tmp_head;
217     *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp);
218   }
219   else {
220     char *value;
221 
222     *date_header = strdup(*date_header);
223     if(!*date_header)
224       goto fail;
225 
226     value = strchr(*date_header, ':');
227     if(!value)
228       goto fail;
229     ++value;
230     while(ISBLANK(*value))
231       ++value;
232     strncpy(timestamp, value, TIMESTAMP_SIZE - 1);
233     timestamp[TIMESTAMP_SIZE - 1] = 0;
234   }
235 
236   /* alpha-sort in a case sensitive manner */
237   do {
238     again = 0;
239     for(l = head; l; l = l->next) {
240       struct curl_slist *next = l->next;
241 
242       if(next && strcmp(l->data, next->data) > 0) {
243         char *tmp = l->data;
244 
245         l->data = next->data;
246         next->data = tmp;
247         again = 1;
248       }
249     }
250   } while(again);
251 
252   for(l = head; l; l = l->next) {
253     char *tmp;
254 
255     if(Curl_dyn_add(canonical_headers, l->data))
256       goto fail;
257     if(Curl_dyn_add(canonical_headers, "\n"))
258       goto fail;
259 
260     tmp = strchr(l->data, ':');
261     if(tmp)
262       *tmp = 0;
263 
264     if(l != head) {
265       if(Curl_dyn_add(signed_headers, ";"))
266         goto fail;
267     }
268     if(Curl_dyn_add(signed_headers, l->data))
269       goto fail;
270   }
271 
272   ret = CURLE_OK;
273 fail:
274   curl_slist_free_all(head);
275 
276   return ret;
277 }
278 
279 #define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
280 /* add 2 for ": " between header name and value */
281 #define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \
282                                 SHA256_HEX_LENGTH)
283 
284 /* try to parse a payload hash from the content-sha256 header */
parse_content_sha_hdr(struct Curl_easy * data,const char * provider1,size_t * value_len)285 static char *parse_content_sha_hdr(struct Curl_easy *data,
286                                    const char *provider1,
287                                    size_t *value_len)
288 {
289   char key[CONTENT_SHA256_KEY_LEN];
290   size_t key_len;
291   char *value;
292   size_t len;
293 
294   key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1);
295 
296   value = Curl_checkheaders(data, key, key_len);
297   if(!value)
298     return NULL;
299 
300   value = strchr(value, ':');
301   if(!value)
302     return NULL;
303   ++value;
304 
305   while(*value && ISBLANK(*value))
306     ++value;
307 
308   len = strlen(value);
309   while(len > 0 && ISBLANK(value[len-1]))
310     --len;
311 
312   *value_len = len;
313   return value;
314 }
315 
calc_payload_hash(struct Curl_easy * data,unsigned char * sha_hash,char * sha_hex)316 static CURLcode calc_payload_hash(struct Curl_easy *data,
317                                   unsigned char *sha_hash, char *sha_hex)
318 {
319   const char *post_data = data->set.postfields;
320   size_t post_data_len = 0;
321   CURLcode result;
322 
323   if(post_data) {
324     if(data->set.postfieldsize < 0)
325       post_data_len = strlen(post_data);
326     else
327       post_data_len = (size_t)data->set.postfieldsize;
328   }
329   result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
330                          post_data_len);
331   if(!result)
332     sha256_to_hex(sha_hex, sha_hash);
333   return result;
334 }
335 
336 #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
337 
calc_s3_payload_hash(struct Curl_easy * data,Curl_HttpReq httpreq,char * provider1,unsigned char * sha_hash,char * sha_hex,char * header)338 static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
339                                      Curl_HttpReq httpreq, char *provider1,
340                                      unsigned char *sha_hash,
341                                      char *sha_hex, char *header)
342 {
343   bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
344   /* The request method or filesize indicate no request payload */
345   bool empty_payload = (empty_method || data->set.filesize == 0);
346   /* The POST payload is in memory */
347   bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
348   CURLcode ret = CURLE_OUT_OF_MEMORY;
349 
350   if(empty_payload || post_payload) {
351     /* Calculate a real hash when we know the request payload */
352     ret = calc_payload_hash(data, sha_hash, sha_hex);
353     if(ret)
354       goto fail;
355   }
356   else {
357     /* Fall back to s3's UNSIGNED-PAYLOAD */
358     size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
359     DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
360     memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
361     sha_hex[len] = 0;
362   }
363 
364   /* format the required content-sha256 header */
365   msnprintf(header, CONTENT_SHA256_HDR_LEN,
366             "x-%s-content-sha256: %s", provider1, sha_hex);
367 
368   ret = CURLE_OK;
369 fail:
370   return ret;
371 }
372 
Curl_output_aws_sigv4(struct Curl_easy * data,bool proxy)373 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
374 {
375   CURLcode ret = CURLE_OUT_OF_MEMORY;
376   struct connectdata *conn = data->conn;
377   size_t len;
378   const char *arg;
379   char provider0[MAX_SIGV4_LEN + 1]="";
380   char provider1[MAX_SIGV4_LEN + 1]="";
381   char region[MAX_SIGV4_LEN + 1]="";
382   char service[MAX_SIGV4_LEN + 1]="";
383   bool sign_as_s3 = false;
384   const char *hostname = conn->host.name;
385   time_t clock;
386   struct tm tm;
387   char timestamp[TIMESTAMP_SIZE];
388   char date[9];
389   struct dynbuf canonical_headers;
390   struct dynbuf signed_headers;
391   char *date_header = NULL;
392   Curl_HttpReq httpreq;
393   const char *method = NULL;
394   char *payload_hash = NULL;
395   size_t payload_hash_len = 0;
396   unsigned char sha_hash[SHA256_DIGEST_LENGTH];
397   char sha_hex[SHA256_HEX_LENGTH];
398   char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
399   char *canonical_request = NULL;
400   char *request_type = NULL;
401   char *credential_scope = NULL;
402   char *str_to_sign = NULL;
403   const char *user = data->state.aptr.user ? data->state.aptr.user : "";
404   char *secret = NULL;
405   unsigned char sign0[SHA256_DIGEST_LENGTH] = {0};
406   unsigned char sign1[SHA256_DIGEST_LENGTH] = {0};
407   char *auth_headers = NULL;
408 
409   DEBUGASSERT(!proxy);
410   (void)proxy;
411 
412   if(Curl_checkheaders(data, STRCONST("Authorization"))) {
413     /* Authorization already present, Bailing out */
414     return CURLE_OK;
415   }
416 
417   /* we init those buffers here, so goto fail will free initialized dynbuf */
418   Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
419   Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
420 
421   /*
422    * Parameters parsing
423    * Google and Outscale use the same OSC or GOOG,
424    * but Amazon uses AWS and AMZ for header arguments.
425    * AWS is the default because most of non-amazon providers
426    * are still using aws:amz as a prefix.
427    */
428   arg = data->set.str[STRING_AWS_SIGV4] ?
429     data->set.str[STRING_AWS_SIGV4] : "aws:amz";
430 
431   /* provider1[:provider2[:region[:service]]]
432 
433      No string can be longer than N bytes of non-whitespace
434    */
435   (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
436                ":%" MAX_SIGV4_LEN_TXT "[^:]"
437                ":%" MAX_SIGV4_LEN_TXT "[^:]"
438                ":%" MAX_SIGV4_LEN_TXT "s",
439                provider0, provider1, region, service);
440   if(!provider0[0]) {
441     failf(data, "first provider can't be empty");
442     ret = CURLE_BAD_FUNCTION_ARGUMENT;
443     goto fail;
444   }
445   else if(!provider1[0])
446     strcpy(provider1, provider0);
447 
448   if(!service[0]) {
449     char *hostdot = strchr(hostname, '.');
450     if(!hostdot) {
451       failf(data, "service missing in parameters and hostname");
452       ret = CURLE_URL_MALFORMAT;
453       goto fail;
454     }
455     len = hostdot - hostname;
456     if(len > MAX_SIGV4_LEN) {
457       failf(data, "service too long in hostname");
458       ret = CURLE_URL_MALFORMAT;
459       goto fail;
460     }
461     strncpy(service, hostname, len);
462     service[len] = '\0';
463 
464     if(!region[0]) {
465       const char *reg = hostdot + 1;
466       const char *hostreg = strchr(reg, '.');
467       if(!hostreg) {
468         failf(data, "region missing in parameters and hostname");
469         ret = CURLE_URL_MALFORMAT;
470         goto fail;
471       }
472       len = hostreg - reg;
473       if(len > MAX_SIGV4_LEN) {
474         failf(data, "region too long in hostname");
475         ret = CURLE_URL_MALFORMAT;
476         goto fail;
477       }
478       strncpy(region, reg, len);
479       region[len] = '\0';
480     }
481   }
482 
483   Curl_http_method(data, conn, &method, &httpreq);
484 
485   /* AWS S3 requires a x-amz-content-sha256 header, and supports special
486    * values like UNSIGNED-PAYLOAD */
487   sign_as_s3 = (strcasecompare(provider0, "aws") &&
488                 strcasecompare(service, "s3"));
489 
490   payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
491 
492   if(!payload_hash) {
493     if(sign_as_s3)
494       ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
495                                  sha_hex, content_sha256_hdr);
496     else
497       ret = calc_payload_hash(data, sha_hash, sha_hex);
498     if(ret)
499       goto fail;
500 
501     payload_hash = sha_hex;
502     /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
503     payload_hash_len = strlen(sha_hex);
504   }
505 
506 #ifdef DEBUGBUILD
507   {
508     char *force_timestamp = getenv("CURL_FORCETIME");
509     if(force_timestamp)
510       clock = 0;
511     else
512       time(&clock);
513   }
514 #else
515   time(&clock);
516 #endif
517   ret = Curl_gmtime(clock, &tm);
518   if(ret) {
519     goto fail;
520   }
521   if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
522     ret = CURLE_OUT_OF_MEMORY;
523     goto fail;
524   }
525 
526   ret = make_headers(data, hostname, timestamp, provider1,
527                      &date_header, content_sha256_hdr,
528                      &canonical_headers, &signed_headers);
529   if(ret)
530     goto fail;
531   ret = CURLE_OUT_OF_MEMORY;
532 
533   if(*content_sha256_hdr) {
534     /* make_headers() needed this without the \r\n for canonicalization */
535     size_t hdrlen = strlen(content_sha256_hdr);
536     DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
537     memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
538   }
539 
540   memcpy(date, timestamp, sizeof(date));
541   date[sizeof(date) - 1] = 0;
542 
543   canonical_request =
544     curl_maprintf("%s\n" /* HTTPRequestMethod */
545                   "%s\n" /* CanonicalURI */
546                   "%s\n" /* CanonicalQueryString */
547                   "%s\n" /* CanonicalHeaders */
548                   "%s\n" /* SignedHeaders */
549                   "%.*s",  /* HashedRequestPayload in hex */
550                   method,
551                   data->state.up.path,
552                   data->state.up.query ? data->state.up.query : "",
553                   Curl_dyn_ptr(&canonical_headers),
554                   Curl_dyn_ptr(&signed_headers),
555                   (int)payload_hash_len, payload_hash);
556   if(!canonical_request)
557     goto fail;
558 
559   /* provider 0 lowercase */
560   Curl_strntolower(provider0, provider0, strlen(provider0));
561   request_type = curl_maprintf("%s4_request", provider0);
562   if(!request_type)
563     goto fail;
564 
565   credential_scope = curl_maprintf("%s/%s/%s/%s",
566                                    date, region, service, request_type);
567   if(!credential_scope)
568     goto fail;
569 
570   if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
571                    strlen(canonical_request)))
572     goto fail;
573 
574   sha256_to_hex(sha_hex, sha_hash);
575 
576   /* provider 0 uppercase */
577   Curl_strntoupper(provider0, provider0, strlen(provider0));
578 
579   /*
580    * Google allows using RSA key instead of HMAC, so this code might change
581    * in the future. For now we only support HMAC.
582    */
583   str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
584                               "%s\n" /* RequestDateTime */
585                               "%s\n" /* CredentialScope */
586                               "%s",  /* HashedCanonicalRequest in hex */
587                               provider0,
588                               timestamp,
589                               credential_scope,
590                               sha_hex);
591   if(!str_to_sign) {
592     goto fail;
593   }
594 
595   /* provider 0 uppercase */
596   secret = curl_maprintf("%s4%s", provider0,
597                          data->state.aptr.passwd ?
598                          data->state.aptr.passwd : "");
599   if(!secret)
600     goto fail;
601 
602   HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
603   HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
604   HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
605   HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
606   HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
607 
608   sha256_to_hex(sha_hex, sign0);
609 
610   /* provider 0 uppercase */
611   auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
612                                "Credential=%s/%s, "
613                                "SignedHeaders=%s, "
614                                "Signature=%s\r\n"
615                                "%s\r\n"
616                                "%s", /* optional sha256 header includes \r\n */
617                                provider0,
618                                user,
619                                credential_scope,
620                                Curl_dyn_ptr(&signed_headers),
621                                sha_hex,
622                                date_header,
623                                content_sha256_hdr);
624   if(!auth_headers) {
625     goto fail;
626   }
627 
628   Curl_safefree(data->state.aptr.userpwd);
629   data->state.aptr.userpwd = auth_headers;
630   data->state.authhost.done = TRUE;
631   ret = CURLE_OK;
632 
633 fail:
634   Curl_dyn_free(&canonical_headers);
635   Curl_dyn_free(&signed_headers);
636   free(canonical_request);
637   free(request_type);
638   free(credential_scope);
639   free(str_to_sign);
640   free(secret);
641   free(date_header);
642   return ret;
643 }
644 
645 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
646