/* This file is part of Eclat. Copyright (C) 2012-2015 Sergey Poznyakoff. Eclat is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Eclat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eclat. If not, see . */ #include #include #include #include "libeclat.h" #include "sha256.h" #include "grecs.h" struct pname { size_t i; char **a; }; static int get_param_name(void *sym, void *data) { struct grecs_syment *se = sym; struct pname *pn = data; pn->a[pn->i++] = se->name; return 0; } static int compnames(const void *a, const void *b) { char * const *ac = a; char * const *bc = b; return strcmp(*ac, *bc); } static void requestsign2(struct ec2_request *req, char *secret) { char **pnames; size_t i, n; struct grecs_txtacc *acc; struct pname pn; char *str; char digest[SHA256_DIGEST_SIZE]; char *signature; size_t siglen; const char *verb; char tsbuf[22]; time_t t; acc = grecs_txtacc_create(); /* Add default parameters */ eclat_request_add_param(req, "AWSAccessKeyId", req->access_key); eclat_request_add_param(req, "SignatureMethod", "HmacSHA256"); eclat_request_add_param(req, "SignatureVersion", "2"); if (req->token) eclat_request_add_param(req, "SecurityToken", req->token); time(&t); strftime(tsbuf, sizeof(tsbuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t)); eclat_request_add_param(req, "Timestamp", tsbuf); eclat_request_encode(req); /* Collect and sort parameter names */ n = grecs_symtab_count_entries(req->params); pnames = grecs_calloc(n, sizeof(pnames[0])); pn.i = 0; pn.a = pnames; grecs_symtab_enumerate(req->params, get_param_name, &pn); qsort(pnames, n, sizeof(pnames[0]), compnames); verb = (req->flags & EC2_RF_POST) ? "POST" : "GET"; grecs_txtacc_grow_string(acc, verb); grecs_txtacc_grow_char(acc, '\n'); grecs_txtacc_grow_string(acc, req->endpoint); grecs_txtacc_grow_char(acc, '\n'); grecs_txtacc_grow_string(acc, req->uri); grecs_txtacc_grow_char(acc, '\n'); /* Append a canonicalized request string */ for (i = 0; i < n; i++) { struct ec2_param *p, key; key.name = pnames[i]; p = grecs_symtab_lookup_or_install(req->params, &key, NULL); if (!p) abort(); if (i != 0) grecs_txtacc_grow_char(acc, '&'); grecs_txtacc_grow_string(acc, p->name); if (p->value) { grecs_txtacc_grow_char(acc, '='); grecs_txtacc_grow_string(acc, p->value); } } grecs_txtacc_grow_char(acc, 0); str = grecs_txtacc_finish(acc, 0); hmac_sha256(str, strlen(str), secret, strlen(secret), digest); eclat_base64_encode((unsigned char *)digest, sizeof(digest), (unsigned char**) &signature, &siglen); eclat_request_add_param_encoded(req, "Signature", signature); free(signature); grecs_txtacc_free(acc); free(pnames); /*FIXME t += req->ttl; strftime(tsbuf, sizeof(tsbuf), "%Y-%m-%dT%H:%M:%SZ", gmtime(&t)); eclat_request_add_param(req, "Expires", tsbuf); */ } void eclat_hex_encode(unsigned char *input, size_t inlen, char **poutput, size_t *poutlen) { size_t l = inlen * 2; char *p = grecs_malloc(l + 1); *poutput = p; *poutlen = l; while (inlen--) { static char xdig[] = "0123456789abcdef"; unsigned c = *input++; *p++ = xdig[c >> 4]; *p++ = xdig[c & 0xf]; } *p = 0; } /* Ref. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html */ static void requestsign4(struct ec2_request *req, char *secret) { char **pnames; struct pname pn; size_t i, n; struct grecs_txtacc *acc; char digest[SHA256_DIGEST_SIZE]; const char *verb; char tsbuf[22]; time_t t; char *p; char const *payload; char *hashstr = NULL; size_t hashsize = 0; char *string_to_sign; static char algostr[] = "AWS4-HMAC-SHA256"; static char termstr[] = "aws4_request"; char *canonical_req; static char *signed_headers = "host;x-amz-date"; char *credential; char *signature; struct sha256_ctx ctx; char *service; size_t service_len; service = req->endpoint; service_len = strcspn(service, "."); /* Create text accumulator */ acc = grecs_txtacc_create(); /* Timestamp */ time(&t); strftime(tsbuf, sizeof(tsbuf), "%Y%m%dT%H%M%SZ", gmtime(&t)); /* Build credential */ grecs_txtacc_grow_string(acc, req->access_key); grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow(acc, tsbuf, 8); /* %Y%m%d part only */ grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow_string(acc, req->region); grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow(acc, service, service_len); grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow_string(acc, termstr); grecs_txtacc_grow_char(acc, 0); credential = grecs_txtacc_finish(acc, 0); eclat_request_add_header(req, "Host", req->endpoint); eclat_request_add_header(req, "X-Amz-Date", tsbuf); /* Encode the request */ eclat_request_encode(req); /* Collect and sort parameter names */ n = grecs_symtab_count_entries(req->params); pnames = grecs_calloc(n, sizeof(pnames[0])); pn.i = 0; pn.a = pnames; grecs_symtab_enumerate(req->params, get_param_name, &pn); qsort(pnames, n, sizeof(pnames[0]), compnames); /* Create a canonical request */ verb = (req->flags & EC2_RF_POST) ? "POST" : "GET"; grecs_txtacc_grow_string(acc, verb); grecs_txtacc_grow_char(acc, '\n'); grecs_txtacc_grow_string(acc, req->uri); grecs_txtacc_grow_char(acc, '\n'); /* Append a canonicalized request string */ for (i = 0; i < n; i++) { struct ec2_param *p, key; key.name = pnames[i]; p = grecs_symtab_lookup_or_install(req->params, &key, NULL); if (!p) abort(); if (i != 0) grecs_txtacc_grow_char(acc, '&'); grecs_txtacc_grow_string(acc, p->name); if (p->value) { grecs_txtacc_grow_char(acc, '='); grecs_txtacc_grow_string(acc, p->value); } } grecs_txtacc_grow_char(acc, '\n'); /* CanonicalHeaders */ grecs_txtacc_grow_string(acc, "host"); grecs_txtacc_grow_char(acc, ':'); grecs_txtacc_grow_string(acc, req->endpoint); grecs_txtacc_grow_char(acc, '\n'); grecs_txtacc_grow_string(acc, "x-amz-date"); grecs_txtacc_grow_char(acc, ':'); grecs_txtacc_grow_string(acc, tsbuf); grecs_txtacc_grow_char(acc, '\n'); /* end of headers */ grecs_txtacc_grow_char(acc, '\n'); /* Signed Headers */ grecs_txtacc_grow_string(acc, signed_headers); grecs_txtacc_grow_char(acc, '\n'); /* Payload hash */ if (req->flags & EC2_RF_POST) { /* FIXME: payload = req->request */ err("%s:%d: POST is not yet implemented", __FILE__, __LINE__); abort(); } else payload = ""; sha256_init_ctx(&ctx); sha256_process_bytes(payload, strlen(payload), &ctx); sha256_finish_ctx(&ctx, digest); eclat_hex_encode((unsigned char *)digest, sizeof(digest), &hashstr, &hashsize); grecs_txtacc_grow(acc, hashstr, hashsize); free(hashstr); grecs_txtacc_grow_char(acc, 0); canonical_req = grecs_txtacc_finish(acc, 0); sha256_init_ctx(&ctx); sha256_process_bytes(canonical_req, strlen(canonical_req), &ctx); sha256_finish_ctx(&ctx, digest); eclat_hex_encode((unsigned char *)digest, sizeof(digest), &canonical_req, &hashsize); /* Create a string to sign */ grecs_txtacc_grow_string(acc, algostr); grecs_txtacc_grow_char(acc, '\n'); grecs_txtacc_grow_string(acc, tsbuf); grecs_txtacc_grow_char(acc, '\n'); /* credential scope: */ grecs_txtacc_grow(acc, tsbuf, 8); /* %Y%m%d part only */ grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow_string(acc, req->region); grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow(acc, service, service_len); grecs_txtacc_grow_char(acc, '/'); grecs_txtacc_grow_string(acc, termstr); grecs_txtacc_grow_char(acc, '\n'); /* hashed request */ grecs_txtacc_grow_string(acc, canonical_req); grecs_txtacc_grow_char(acc, 0); string_to_sign = grecs_txtacc_finish(acc, 0); /* Derive a signing key */ grecs_txtacc_grow_string(acc, "AWS4"); grecs_txtacc_grow_string(acc, secret); grecs_txtacc_grow_char(acc, 0); p = grecs_txtacc_finish(acc, 0); hmac_sha256(tsbuf, 8, p, strlen(p), digest); hmac_sha256(req->region, strlen(req->region), digest, sizeof(digest), digest); hmac_sha256(service, service_len, digest, sizeof(digest), digest); hmac_sha256(termstr, strlen(termstr), digest, sizeof(digest), digest); /* Calculate the signature */ hmac_sha256(string_to_sign, strlen(string_to_sign), digest, sizeof(digest), digest); eclat_hex_encode((unsigned char *)digest, sizeof(digest), &signature, &hashsize); if (req->token) eclat_request_add_header(req, "X-Amz-Security-Token", req->token); /* Build authorization header */ grecs_txtacc_grow_string(acc, algostr); grecs_txtacc_grow_string(acc, " Credential="); grecs_txtacc_grow_string(acc, credential); grecs_txtacc_grow_string(acc, ", SignedHeaders="); grecs_txtacc_grow_string(acc, signed_headers); grecs_txtacc_grow_string(acc, ", Signature="); grecs_txtacc_grow_string(acc, signature); grecs_txtacc_grow_char(acc, 0); p = grecs_txtacc_finish(acc, 0); eclat_request_add_header(req, "Authorization", p); free(signature); grecs_txtacc_free(acc); /* Encode the request */ eclat_request_encode(req); } struct qsimpl { char *qs_version; void (*qs_fun)(struct ec2_request *, char *); }; static struct qsimpl qstab[] = { { "2", requestsign2 }, { "4", requestsign4 }, { NULL } }; void eclat_request_sign(struct ec2_request *req, char *secret, char *version) { struct qsimpl *qs; for (qs = qstab; qs->qs_version && strcmp(qs->qs_version, version); qs++) ; if (qs->qs_version) qs->qs_fun(req, secret); else { err("INTERNAL ERROR: unsupported version %s", version); abort(); } }