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
36#include "strcase.h"
37#include "parsedate.h"
38#include "sendf.h"
39
40#include <time.h>
41
42/* The last 3 #include files should be in this order */
43#include "curl_printf.h"
44#include "curl_memory.h"
45#include "memdebug.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 != CURLE_OK) { \
55 goto fail; \
56 } \
57 } while(0)
58
59static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
60{
61 int i;
62
63 DEBUGASSERT(dst_l >= 65);
64 for(i = 0; i < 32; ++i) {
65 curl_msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]);
66 }
67}
68
69CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
70{
71 CURLcode ret = CURLE_OUT_OF_MEMORY;
72 struct connectdata *conn = data->conn;
73 size_t len;
74 const char *tmp0;
75 const char *tmp1;
76 char *provider0_low = NULL;
77 char *provider0_up = NULL;
78 char *provider1_low = NULL;
79 char *provider1_mid = NULL;
80 char *region = NULL;
81 char *service = NULL;
82 const char *hostname = conn->host.name;
83#ifdef DEBUGBUILD
84 char *force_timestamp;
85#endif
86 time_t clock;
87 struct tm tm;
88 char timestamp[17];
89 char date[9];
90 const char *content_type = Curl_checkheaders(data, STRCONST("Content-Type"));
91 char *canonical_headers = NULL;
92 char *signed_headers = NULL;
93 Curl_HttpReq httpreq;
94 const char *method;
95 size_t post_data_len;
96 const char *post_data = data->set.postfields ? data->set.postfields : "";
97 unsigned char sha_hash[32];
98 char sha_hex[65];
99 char *canonical_request = NULL;
100 char *request_type = NULL;
101 char *credential_scope = NULL;
102 char *str_to_sign = NULL;
103 const char *user = data->state.aptr.user ? data->state.aptr.user : "";
104 const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : "";
105 char *secret = NULL;
106 unsigned char tmp_sign0[32] = {0};
107 unsigned char tmp_sign1[32] = {0};
108 char *auth_headers = NULL;
109
110 DEBUGASSERT(!proxy);
111 (void)proxy;
112
113 if(Curl_checkheaders(data, STRCONST("Authorization"))) {
114 /* Authorization already present, Bailing out */
115 return CURLE_OK;
116 }
117
118 /*
119 * Parameters parsing
120 * Google and Outscale use the same OSC or GOOG,
121 * but Amazon uses AWS and AMZ for header arguments.
122 * AWS is the default because most of non-amazon providers
123 * are still using aws:amz as a prefix.
124 */
125 tmp0 = data->set.str[STRING_AWS_SIGV4] ?
126 data->set.str[STRING_AWS_SIGV4] : "aws:amz";
127 tmp1 = strchr(tmp0, ':');
128 len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
129 if(len < 1) {
130 infof(data, "first provider can't be empty");
131 ret = CURLE_BAD_FUNCTION_ARGUMENT;
132 goto fail;
133 }
134 provider0_low = malloc(len + 1);
135 provider0_up = malloc(len + 1);
136 if(!provider0_low || !provider0_up) {
137 goto fail;
138 }
139 Curl_strntolower(provider0_low, tmp0, len);
140 provider0_low[len] = '\0';
141 Curl_strntoupper(provider0_up, tmp0, len);
142 provider0_up[len] = '\0';
143
144 if(tmp1) {
145 tmp0 = tmp1 + 1;
146 tmp1 = strchr(tmp0, ':');
147 len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
148 if(len < 1) {
149 infof(data, "second provider can't be empty");
150 ret = CURLE_BAD_FUNCTION_ARGUMENT;
151 goto fail;
152 }
153 provider1_low = malloc(len + 1);
154 provider1_mid = malloc(len + 1);
155 if(!provider1_low || !provider1_mid) {
156 goto fail;
157 }
158 Curl_strntolower(provider1_low, tmp0, len);
159 provider1_low[len] = '\0';
160 Curl_strntolower(provider1_mid, tmp0, len);
161 provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]);
162 provider1_mid[len] = '\0';
163
164 if(tmp1) {
165 tmp0 = tmp1 + 1;
166 tmp1 = strchr(tmp0, ':');
167 len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
168 if(len < 1) {
169 infof(data, "region can't be empty");
170 ret = CURLE_BAD_FUNCTION_ARGUMENT;
171 goto fail;
172 }
173 region = Curl_memdup(tmp0, len + 1);
174 if(!region) {
175 goto fail;
176 }
177 region[len] = '\0';
178
179 if(tmp1) {
180 tmp0 = tmp1 + 1;
181 service = strdup(tmp0);
182 if(!service) {
183 goto fail;
184 }
185 if(strlen(service) < 1) {
186 infof(data, "service can't be empty");
187 ret = CURLE_BAD_FUNCTION_ARGUMENT;
188 goto fail;
189 }
190 }
191 }
192 }
193 else {
194 provider1_low = Curl_memdup(provider0_low, len + 1);
195 provider1_mid = Curl_memdup(provider0_low, len + 1);
196 if(!provider1_low || !provider1_mid) {
197 goto fail;
198 }
199 provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]);
200 }
201
202 if(!service) {
203 tmp0 = hostname;
204 tmp1 = strchr(tmp0, '.');
205 if(!tmp1) {
206 infof(data, "service missing in parameters or hostname");
207 ret = CURLE_URL_MALFORMAT;
208 goto fail;
209 }
210 len = tmp1 - tmp0;
211 service = Curl_memdup(tmp0, len + 1);
212 if(!service) {
213 goto fail;
214 }
215 service[len] = '\0';
216
217 if(!region) {
218 tmp0 = tmp1 + 1;
219 tmp1 = strchr(tmp0, '.');
220 if(!tmp1) {
221 infof(data, "region missing in parameters or hostname");
222 ret = CURLE_URL_MALFORMAT;
223 goto fail;
224 }
225 len = tmp1 - tmp0;
226 region = Curl_memdup(tmp0, len + 1);
227 if(!region) {
228 goto fail;
229 }
230 region[len] = '\0';
231 }
232 }
233
234#ifdef DEBUGBUILD
235 force_timestamp = getenv("CURL_FORCETIME");
236 if(force_timestamp)
237 clock = 0;
238 else
239 time(&clock);
240#else
241 time(&clock);
242#endif
243 ret = Curl_gmtime(clock, &tm);
244 if(ret != CURLE_OK) {
245 goto fail;
246 }
247 if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
248 goto fail;
249 }
250 memcpy(date, timestamp, sizeof(date));
251 date[sizeof(date) - 1] = 0;
252
253 if(content_type) {
254 content_type = strchr(content_type, ':');
255 if(!content_type) {
256 ret = CURLE_FAILED_INIT;
257 goto fail;
258 }
259 content_type++;
260 /* Skip whitespace now */
261 while(*content_type == ' ' || *content_type == '\t')
262 ++content_type;
263
264 canonical_headers = curl_maprintf("content-type:%s\n"
265 "host:%s\n"
266 "x-%s-date:%s\n",
267 content_type,
268 hostname,
269 provider1_low, timestamp);
270 signed_headers = curl_maprintf("content-type;host;x-%s-date",
271 provider1_low);
272 }
273 else {
274 canonical_headers = curl_maprintf("host:%s\n"
275 "x-%s-date:%s\n",
276 hostname,
277 provider1_low, timestamp);
278 signed_headers = curl_maprintf("host;x-%s-date", provider1_low);
279 }
280
281 if(!canonical_headers || !signed_headers) {
282 goto fail;
283 }
284
285 if(data->set.postfieldsize < 0)
286 post_data_len = strlen(post_data);
287 else
288 post_data_len = (size_t)data->set.postfieldsize;
289 if(Curl_sha256it(sha_hash, (const unsigned char *) post_data,
290 post_data_len)) {
291 goto fail;
292 }
293
294 sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
295
296 Curl_http_method(data, conn, &method, &httpreq);
297
298 canonical_request =
299 curl_maprintf("%s\n" /* HTTPRequestMethod */
300 "%s\n" /* CanonicalURI */
301 "%s\n" /* CanonicalQueryString */
302 "%s\n" /* CanonicalHeaders */
303 "%s\n" /* SignedHeaders */
304 "%s", /* HashedRequestPayload in hex */
305 method,
306 data->state.up.path,
307 data->state.up.query ? data->state.up.query : "",
308 canonical_headers,
309 signed_headers,
310 sha_hex);
311 if(!canonical_request) {
312 goto fail;
313 }
314
315 request_type = curl_maprintf("%s4_request", provider0_low);
316 if(!request_type) {
317 goto fail;
318 }
319
320 credential_scope = curl_maprintf("%s/%s/%s/%s",
321 date, region, service, request_type);
322 if(!credential_scope) {
323 goto fail;
324 }
325
326 if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
327 strlen(canonical_request))) {
328 goto fail;
329 }
330
331 sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
332
333 /*
334 * Google allow to use rsa key instead of HMAC, so this code might change
335 * In the future, but for now we support only HMAC version
336 */
337 str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
338 "%s\n" /* RequestDateTime */
339 "%s\n" /* CredentialScope */
340 "%s", /* HashedCanonicalRequest in hex */
341 provider0_up,
342 timestamp,
343 credential_scope,
344 sha_hex);
345 if(!str_to_sign) {
346 goto fail;
347 }
348
349 secret = curl_maprintf("%s4%s", provider0_up, passwd);
350 if(!secret) {
351 goto fail;
352 }
353
354 HMAC_SHA256(secret, strlen(secret),
355 date, strlen(date), tmp_sign0);
356 HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0),
357 region, strlen(region), tmp_sign1);
358 HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1),
359 service, strlen(service), tmp_sign0);
360 HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0),
361 request_type, strlen(request_type), tmp_sign1);
362 HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1),
363 str_to_sign, strlen(str_to_sign), tmp_sign0);
364
365 sha256_to_hex(sha_hex, tmp_sign0, sizeof(sha_hex));
366
367 auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
368 "Credential=%s/%s, "
369 "SignedHeaders=%s, "
370 "Signature=%s\r\n"
371 "X-%s-Date: %s\r\n",
372 provider0_up,
373 user,
374 credential_scope,
375 signed_headers,
376 sha_hex,
377 provider1_mid,
378 timestamp);
379 if(!auth_headers) {
380 goto fail;
381 }
382
383 Curl_safefree(data->state.aptr.userpwd);
384 data->state.aptr.userpwd = auth_headers;
385 data->state.authhost.done = TRUE;
386 ret = CURLE_OK;
387
388fail:
389 free(provider0_low);
390 free(provider0_up);
391 free(provider1_low);
392 free(provider1_mid);
393 free(region);
394 free(service);
395 free(canonical_headers);
396 free(signed_headers);
397 free(canonical_request);
398 free(request_type);
399 free(credential_scope);
400 free(str_to_sign);
401 free(secret);
402 return ret;
403}
404
405#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
406