VirtualBox

source: vbox/trunk/src/libs/curl-7.87.0/lib/http_aws_sigv4.c@ 98326

Last change on this file since 98326 was 98326, checked in by vboxsync, 2 years ago

curl-7.87.0: Applied and adjusted our curl changes to 7.83.1. bugref:10356

  • Property svn:eol-style set to native
File size: 15.6 KB
Line 
1/***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2022, Daniel Stenberg, <[email protected]>, 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 (unsigned int)kl, \
52 (unsigned char *)d, \
53 (unsigned int)dl, o); \
54 if(ret) { \
55 goto fail; \
56 } \
57 } while(0)
58
59#define TIMESTAMP_SIZE 17
60
61static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
62{
63 int i;
64
65 DEBUGASSERT(dst_l >= 65);
66 for(i = 0; i < 32; ++i) {
67 msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]);
68 }
69}
70
71static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
72{
73 char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
74
75 if(tmp)
76 return tmp;
77 return Curl_checkheaders(data, STRCONST("Date"));
78}
79
80/* remove whitespace, and lowercase all headers */
81static void trim_headers(struct curl_slist *head)
82{
83 struct curl_slist *l;
84 for(l = head; l; l = l->next) {
85 char *value; /* to read from */
86 char *store;
87 size_t colon = strcspn(l->data, ":");
88 Curl_strntolower(l->data, l->data, colon);
89
90 value = &l->data[colon];
91 if(!*value)
92 continue;
93 ++value;
94 store = value;
95
96 /* skip leading whitespace */
97 while(*value && ISBLANK(*value))
98 value++;
99
100 while(*value) {
101 int space = 0;
102 while(*value && ISBLANK(*value)) {
103 value++;
104 space++;
105 }
106 if(space) {
107 /* replace any number of consecutive whitespace with a single space,
108 unless at the end of the string, then nothing */
109 if(*value)
110 *store++ = ' ';
111 }
112 else
113 *store++ = *value++;
114 }
115 *store = 0; /* null terminate */
116 }
117}
118
119/* maximum length for the aws sivg4 parts */
120#define MAX_SIGV4_LEN 64
121#define MAX_SIGV4_LEN_TXT "64"
122
123#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
124
125#define MAX_HOST_LEN 255
126/* FQDN + host: */
127#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:"))
128
129/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
130#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
131
132/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
133static CURLcode make_headers(struct Curl_easy *data,
134 const char *hostname,
135 char *timestamp,
136 char *provider1,
137 char **date_header,
138 struct dynbuf *canonical_headers,
139 struct dynbuf *signed_headers)
140{
141 char date_hdr_key[DATE_HDR_KEY_LEN];
142 char date_full_hdr[DATE_FULL_HDR_LEN];
143 struct curl_slist *head = NULL;
144 struct curl_slist *tmp_head = NULL;
145 CURLcode ret = CURLE_OUT_OF_MEMORY;
146 struct curl_slist *l;
147 int again = 1;
148
149 /* provider1 mid */
150 Curl_strntolower(provider1, provider1, strlen(provider1));
151 provider1[0] = Curl_raw_toupper(provider1[0]);
152
153 msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1);
154
155 /* provider1 lowercase */
156 Curl_strntolower(provider1, provider1, 1); /* first byte only */
157 msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
158 "x-%s-date:%s", provider1, timestamp);
159
160 if(Curl_checkheaders(data, STRCONST("Host"))) {
161 head = NULL;
162 }
163 else {
164 char full_host[FULL_HOST_LEN + 1];
165
166 if(data->state.aptr.host) {
167 size_t pos;
168
169 if(strlen(data->state.aptr.host) > FULL_HOST_LEN) {
170 ret = CURLE_URL_MALFORMAT;
171 goto fail;
172 }
173 strcpy(full_host, data->state.aptr.host);
174 /* remove /r/n as the separator for canonical request must be '\n' */
175 pos = strcspn(full_host, "\n\r");
176 full_host[pos] = 0;
177 }
178 else {
179 if(strlen(hostname) > MAX_HOST_LEN) {
180 ret = CURLE_URL_MALFORMAT;
181 goto fail;
182 }
183 msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname);
184 }
185
186 head = curl_slist_append(NULL, full_host);
187 if(!head)
188 goto fail;
189 }
190
191
192 for(l = data->set.headers; l; l = l->next) {
193 tmp_head = curl_slist_append(head, l->data);
194 if(!tmp_head)
195 goto fail;
196 head = tmp_head;
197 }
198
199 trim_headers(head);
200
201 *date_header = find_date_hdr(data, date_hdr_key);
202 if(!*date_header) {
203 tmp_head = curl_slist_append(head, date_full_hdr);
204 if(!tmp_head)
205 goto fail;
206 head = tmp_head;
207 *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp);
208 }
209 else {
210 char *value;
211
212 *date_header = strdup(*date_header);
213 if(!*date_header)
214 goto fail;
215
216 value = strchr(*date_header, ':');
217 if(!value)
218 goto fail;
219 ++value;
220 while(ISBLANK(*value))
221 ++value;
222 strncpy(timestamp, value, TIMESTAMP_SIZE - 1);
223 timestamp[TIMESTAMP_SIZE - 1] = 0;
224 }
225
226 /* alpha-sort in a case sensitive manner */
227 do {
228 again = 0;
229 for(l = head; l; l = l->next) {
230 struct curl_slist *next = l->next;
231
232 if(next && strcmp(l->data, next->data) > 0) {
233 char *tmp = l->data;
234
235 l->data = next->data;
236 next->data = tmp;
237 again = 1;
238 }
239 }
240 } while(again);
241
242 for(l = head; l; l = l->next) {
243 char *tmp;
244
245 if(Curl_dyn_add(canonical_headers, l->data))
246 goto fail;
247 if(Curl_dyn_add(canonical_headers, "\n"))
248 goto fail;
249
250 tmp = strchr(l->data, ':');
251 if(tmp)
252 *tmp = 0;
253
254 if(l != head) {
255 if(Curl_dyn_add(signed_headers, ";"))
256 goto fail;
257 }
258 if(Curl_dyn_add(signed_headers, l->data))
259 goto fail;
260 }
261
262 ret = CURLE_OK;
263fail:
264 curl_slist_free_all(head);
265
266 return ret;
267}
268
269#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
270
271/* try to parse a payload hash from the content-sha256 header */
272static char *parse_content_sha_hdr(struct Curl_easy *data,
273 const char *provider1,
274 size_t *value_len)
275{
276 char key[CONTENT_SHA256_KEY_LEN];
277 size_t key_len;
278 char *value;
279 size_t len;
280
281 key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1);
282
283 value = Curl_checkheaders(data, key, key_len);
284 if(!value)
285 return NULL;
286
287 value = strchr(value, ':');
288 if(!value)
289 return NULL;
290 ++value;
291
292 while(*value && ISBLANK(*value))
293 ++value;
294
295 len = strlen(value);
296 while(len > 0 && ISBLANK(value[len-1]))
297 --len;
298
299 *value_len = len;
300 return value;
301}
302
303CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
304{
305 CURLcode ret = CURLE_OUT_OF_MEMORY;
306 struct connectdata *conn = data->conn;
307 size_t len;
308 const char *arg;
309 char provider0[MAX_SIGV4_LEN + 1]="";
310 char provider1[MAX_SIGV4_LEN + 1]="";
311 char region[MAX_SIGV4_LEN + 1]="";
312 char service[MAX_SIGV4_LEN + 1]="";
313 const char *hostname = conn->host.name;
314 time_t clock;
315 struct tm tm;
316 char timestamp[TIMESTAMP_SIZE];
317 char date[9];
318 struct dynbuf canonical_headers;
319 struct dynbuf signed_headers;
320 char *date_header = NULL;
321 char *payload_hash = NULL;
322 size_t payload_hash_len = 0;
323 const char *post_data = data->set.postfields;
324 size_t post_data_len = 0;
325 unsigned char sha_hash[32];
326 char sha_hex[65];
327 char *canonical_request = NULL;
328 char *request_type = NULL;
329 char *credential_scope = NULL;
330 char *str_to_sign = NULL;
331 const char *user = data->state.aptr.user ? data->state.aptr.user : "";
332 char *secret = NULL;
333 unsigned char sign0[32] = {0};
334 unsigned char sign1[32] = {0};
335 char *auth_headers = NULL;
336
337 DEBUGASSERT(!proxy);
338 (void)proxy;
339
340 if(Curl_checkheaders(data, STRCONST("Authorization"))) {
341 /* Authorization already present, Bailing out */
342 return CURLE_OK;
343 }
344
345 /* we init those buffers here, so goto fail will free initialized dynbuf */
346 Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
347 Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
348
349 /*
350 * Parameters parsing
351 * Google and Outscale use the same OSC or GOOG,
352 * but Amazon uses AWS and AMZ for header arguments.
353 * AWS is the default because most of non-amazon providers
354 * are still using aws:amz as a prefix.
355 */
356 arg = data->set.str[STRING_AWS_SIGV4] ?
357 data->set.str[STRING_AWS_SIGV4] : "aws:amz";
358
359 /* provider1[:provider2[:region[:service]]]
360
361 No string can be longer than N bytes of non-whitespace
362 */
363 (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
364 ":%" MAX_SIGV4_LEN_TXT "[^:]"
365 ":%" MAX_SIGV4_LEN_TXT "[^:]"
366 ":%" MAX_SIGV4_LEN_TXT "s",
367 provider0, provider1, region, service);
368 if(!provider0[0]) {
369 failf(data, "first provider can't be empty");
370 ret = CURLE_BAD_FUNCTION_ARGUMENT;
371 goto fail;
372 }
373 else if(!provider1[0])
374 strcpy(provider1, provider0);
375
376 if(!service[0]) {
377 char *hostdot = strchr(hostname, '.');
378 if(!hostdot) {
379 failf(data, "service missing in parameters and hostname");
380 ret = CURLE_URL_MALFORMAT;
381 goto fail;
382 }
383 len = hostdot - hostname;
384 if(len > MAX_SIGV4_LEN) {
385 failf(data, "service too long in hostname");
386 ret = CURLE_URL_MALFORMAT;
387 goto fail;
388 }
389 strncpy(service, hostname, len);
390 service[len] = '\0';
391
392 if(!region[0]) {
393 const char *reg = hostdot + 1;
394 const char *hostreg = strchr(reg, '.');
395 if(!hostreg) {
396 failf(data, "region missing in parameters and hostname");
397 ret = CURLE_URL_MALFORMAT;
398 goto fail;
399 }
400 len = hostreg - reg;
401 if(len > MAX_SIGV4_LEN) {
402 failf(data, "region too long in hostname");
403 ret = CURLE_URL_MALFORMAT;
404 goto fail;
405 }
406 strncpy(region, reg, len);
407 region[len] = '\0';
408 }
409 }
410
411#ifdef DEBUGBUILD
412 {
413 char *force_timestamp = getenv("CURL_FORCETIME");
414 if(force_timestamp)
415 clock = 0;
416 else
417 time(&clock);
418 }
419#else
420 time(&clock);
421#endif
422 ret = Curl_gmtime(clock, &tm);
423 if(ret) {
424 goto fail;
425 }
426 if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
427 ret = CURLE_OUT_OF_MEMORY;
428 goto fail;
429 }
430
431 ret = make_headers(data, hostname, timestamp, provider1,
432 &date_header, &canonical_headers, &signed_headers);
433 if(ret)
434 goto fail;
435 ret = CURLE_OUT_OF_MEMORY;
436
437 memcpy(date, timestamp, sizeof(date));
438 date[sizeof(date) - 1] = 0;
439
440 payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
441
442 if(!payload_hash) {
443 if(post_data) {
444 if(data->set.postfieldsize < 0)
445 post_data_len = strlen(post_data);
446 else
447 post_data_len = (size_t)data->set.postfieldsize;
448 }
449 if(Curl_sha256it(sha_hash, (const unsigned char *) post_data,
450 post_data_len))
451 goto fail;
452
453 sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
454 payload_hash = sha_hex;
455 payload_hash_len = strlen(sha_hex);
456 }
457
458 {
459 Curl_HttpReq httpreq;
460 const char *method;
461
462 Curl_http_method(data, conn, &method, &httpreq);
463
464 canonical_request =
465 curl_maprintf("%s\n" /* HTTPRequestMethod */
466 "%s\n" /* CanonicalURI */
467 "%s\n" /* CanonicalQueryString */
468 "%s\n" /* CanonicalHeaders */
469 "%s\n" /* SignedHeaders */
470 "%.*s", /* HashedRequestPayload in hex */
471 method,
472 data->state.up.path,
473 data->state.up.query ? data->state.up.query : "",
474 Curl_dyn_ptr(&canonical_headers),
475 Curl_dyn_ptr(&signed_headers),
476 (int)payload_hash_len, payload_hash);
477 if(!canonical_request)
478 goto fail;
479 }
480
481 /* provider 0 lowercase */
482 Curl_strntolower(provider0, provider0, strlen(provider0));
483 request_type = curl_maprintf("%s4_request", provider0);
484 if(!request_type)
485 goto fail;
486
487 credential_scope = curl_maprintf("%s/%s/%s/%s",
488 date, region, service, request_type);
489 if(!credential_scope)
490 goto fail;
491
492 if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
493 strlen(canonical_request)))
494 goto fail;
495
496 sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
497
498 /* provider 0 uppercase */
499 Curl_strntoupper(provider0, provider0, strlen(provider0));
500
501 /*
502 * Google allows using RSA key instead of HMAC, so this code might change
503 * in the future. For now we only support HMAC.
504 */
505 str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
506 "%s\n" /* RequestDateTime */
507 "%s\n" /* CredentialScope */
508 "%s", /* HashedCanonicalRequest in hex */
509 provider0,
510 timestamp,
511 credential_scope,
512 sha_hex);
513 if(!str_to_sign) {
514 goto fail;
515 }
516
517 /* provider 0 uppercase */
518 secret = curl_maprintf("%s4%s", provider0,
519 data->state.aptr.passwd ?
520 data->state.aptr.passwd : "");
521 if(!secret)
522 goto fail;
523
524 HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
525 HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
526 HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
527 HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
528 HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
529
530 sha256_to_hex(sha_hex, sign0, sizeof(sha_hex));
531
532 /* provider 0 uppercase */
533 auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
534 "Credential=%s/%s, "
535 "SignedHeaders=%s, "
536 "Signature=%s\r\n"
537 "%s\r\n",
538 provider0,
539 user,
540 credential_scope,
541 Curl_dyn_ptr(&signed_headers),
542 sha_hex,
543 date_header);
544 if(!auth_headers) {
545 goto fail;
546 }
547
548 Curl_safefree(data->state.aptr.userpwd);
549 data->state.aptr.userpwd = auth_headers;
550 data->state.authhost.done = TRUE;
551 ret = CURLE_OK;
552
553fail:
554 Curl_dyn_free(&canonical_headers);
555 Curl_dyn_free(&signed_headers);
556 free(canonical_request);
557 free(request_type);
558 free(credential_scope);
559 free(str_to_sign);
560 free(secret);
561 free(date_header);
562 return ret;
563}
564
565#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette