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