diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2014-07-09 13:01:57 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2014-07-09 23:20:17 +0300 |
commit | 131b6ab56cbec838346fd493f3fe96438e3b58e7 (patch) | |
tree | 1a30a33f6447fcbeba9810542121dc7003d3129a /lib | |
parent | 7f40bb8674983f8e4fc11fbebe56f88daa812c1a (diff) | |
download | eclat-131b6ab56cbec838346fd493f3fe96438e3b58e7.tar.gz eclat-131b6ab56cbec838346fd493f3fe96438e3b58e7.tar.bz2 |
Implement signature version 4 signing process
* lib/libeclat.h (ec2_param) <encoded>: New member.
(ec2_query) <signature>: Remove.
<headers,region,access_key>: New members
(eclat_query_create): Take two more arguments. All uses changed.
(eclat_query_add_param_encoded)
(eclat_query_add_header): New functions.
* lib/q2url.c (eclat_query_to_url): Don't create Signature param:
it is already in the param list (for v2 process).
* lib/qaddparm.c (eclat_query_add_param_encoded): New function.
(eclat_query_add_header): New function.
* lib/qcreat.c (eclat_query_create): Take region and access key
as additional parameters.
* lib/qencode.c (encode_param): Skip parameters that have encoded
set to true.
* lib/reqsign.c (querysign2): Store access key in AWSAccessKeyId
and the generated signature in the Signature parameters.
(eclat_hex_encode): New function.
(querysign4): Implement signature version 4 signing process.
* src/ec2map.c: Update call to eclat_query_create.
* src/eclat.c: Likewise.
* src/util.c (eclat_send_query): Sign the query and add
requested headers prior to sending.
* doc/eclat.conf.5: Document signature-version.
* NEWS: Likewise.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libeclat.h | 12 | ||||
-rw-r--r-- | lib/q2url.c | 33 | ||||
-rw-r--r-- | lib/qaddparm.c | 48 | ||||
-rw-r--r-- | lib/qcreat.c | 5 | ||||
-rw-r--r-- | lib/qencode.c | 3 | ||||
-rw-r--r-- | lib/reqsign.c | 230 |
6 files changed, 303 insertions, 28 deletions
diff --git a/lib/libeclat.h b/lib/libeclat.h index 51728e1..b7dc189 100644 --- a/lib/libeclat.h +++ b/lib/libeclat.h @@ -70,6 +70,7 @@ int eclat_base64_decode(const unsigned char *input, size_t input_len, struct ec2_param { char *name; char *value; + int encoded; }; #define EC2_API_VERSION "2013-02-01" @@ -82,15 +83,22 @@ struct ec2_query { char *endpoint; /* endpoint */ char *uri; /* URI without parameters */ struct grecs_symtab *params; /* Query parameters */ - char *signature; + struct grecs_list *headers; /* Query headers */ + char *region; + char *access_key; unsigned long ttl; /* Time-to-live in seconds */ }; struct ec2_query *eclat_query_create(int flags, const char *endpoint, - const char *uri); + const char *uri, char const *region, + char const *access_key); void eclat_query_free(struct ec2_query *); void eclat_query_add_param(struct ec2_query *q, const char *name, const char *value); +void eclat_query_add_param_encoded(struct ec2_query *q, const char *name, + const char *value); +void eclat_query_add_header(struct ec2_query *q, const char *name, + const char *value); void eclat_query_sign(struct ec2_query *req, char *secret, char *version); diff --git a/lib/q2url.c b/lib/q2url.c index f5d8863..da0ebdc 100644 --- a/lib/q2url.c +++ b/lib/q2url.c @@ -19,17 +19,24 @@ #include "libeclat.h" #include "grecs.h" +struct param_closure { + struct grecs_txtacc *acc; + int count; +}; + static int add_param(void *sym, void *data) { struct ec2_param *p = sym; - struct grecs_txtacc *acc = data; + struct param_closure *pc = data; - grecs_txtacc_grow_char(acc, '&'); - grecs_txtacc_grow(acc, p->name, strlen(p->name)); + if (pc->count) + grecs_txtacc_grow_char(pc->acc, '&'); + ++pc->count; + grecs_txtacc_grow_string(pc->acc, p->name); if (p->value) { - grecs_txtacc_grow_char(acc, '='); - grecs_txtacc_grow(acc, p->value, strlen(p->value)); + grecs_txtacc_grow_char(pc->acc, '='); + grecs_txtacc_grow_string(pc->acc, p->value); } return 0; @@ -40,7 +47,8 @@ eclat_query_to_url(struct ec2_query *req, char **post_params) { struct grecs_txtacc *acc; char *ret = NULL, *p; - + struct param_closure pc; + acc = grecs_txtacc_create(); grecs_txtacc_grow(acc, "http", 4); @@ -57,16 +65,10 @@ eclat_query_to_url(struct ec2_query *req, char **post_params) grecs_txtacc_grow_char(acc, '?'); } - /* Add signature */ - grecs_txtacc_grow(acc, "Signature", sizeof("Signature")-1); - grecs_txtacc_grow_char(acc, '='); - - urlencode(req->signature, strlen(req->signature), &p, NULL); - grecs_txtacc_grow(acc, p, strlen(p)); - free(p); - /* Add other parameters */ - grecs_symtab_enumerate(req->params, add_param, acc); + pc.acc = acc; + pc.count = 0; + grecs_symtab_enumerate(req->params, add_param, &pc); grecs_txtacc_grow_char(acc, 0); @@ -79,3 +81,4 @@ eclat_query_to_url(struct ec2_query *req, char **post_params) return ret; } + diff --git a/lib/qaddparm.c b/lib/qaddparm.c index 6048d32..e1e339a 100644 --- a/lib/qaddparm.c +++ b/lib/qaddparm.c @@ -20,7 +20,8 @@ #include "grecs.h" void -eclat_query_add_param(struct ec2_query *q, const char *name, const char *value) +eclat_query_add_param0(struct ec2_query *q, const char *name, + const char *value, int encoded) { struct ec2_param *p, key; int install = 1; @@ -30,4 +31,49 @@ eclat_query_add_param(struct ec2_query *q, const char *name, const char *value) if (!install) free(p->value); p->value = value ? grecs_strdup(value) : NULL; + p->encoded = encoded; +} + +void +eclat_query_add_param(struct ec2_query *q, const char *name, const char *value) +{ + eclat_query_add_param0(q, name, value, 0); +} + +void +eclat_query_add_param_encoded(struct ec2_query *q, const char *name, + const char *value) +{ + if (value) { + char *str; + urlencode(value, strlen(value), &str, NULL); + eclat_query_add_param0(q, name, str, 1); + free(str); + } else + eclat_query_add_param0(q, name, value, 1); +} + + +static void +free_header(void *data) +{ + struct ec2_param *p = data; + free(p->name); + free(p->value); + free(p); +} + +void +eclat_query_add_header(struct ec2_query *q, const char *name, const char *value) +{ + struct ec2_param *ent; + + if (!q->headers) { + q->headers = grecs_list_create(); + q->headers->free_entry = free_header; + } + ent = grecs_malloc(sizeof(*ent)); + ent->name = grecs_strdup(name); + ent->value = value ? grecs_strdup(value) : NULL; + grecs_list_append(q->headers, ent); } diff --git a/lib/qcreat.c b/lib/qcreat.c index c90c3fe..32471a0 100644 --- a/lib/qcreat.c +++ b/lib/qcreat.c @@ -28,7 +28,8 @@ ec2_param_free(void *ptr) } struct ec2_query * -eclat_query_create(int flags, const char *endpoint, const char *uri) +eclat_query_create(int flags, const char *endpoint, const char *uri, + char const *region, char const *access_key) { struct ec2_query *q = grecs_zalloc(sizeof(*q)); q->flags = flags; @@ -42,5 +43,7 @@ eclat_query_create(int flags, const char *endpoint, const char *uri) q->endpoint = grecs_strdup(endpoint); q->uri = grecs_strdup(uri); q->ttl = 5; + q->region = grecs_strdup(region ? region : "us-east-1"); + q->access_key = grecs_strdup(access_key); return q; } diff --git a/lib/qencode.c b/lib/qencode.c index a0f6ab6..d30ffd1 100644 --- a/lib/qencode.c +++ b/lib/qencode.c @@ -25,10 +25,11 @@ encode_param(void *sym, void *data) struct ec2_param *p = sym; char *enc; - if (p->value) { + if (!p->encoded && p->value) { urlencode(p->value, strlen(p->value), &enc, NULL); free(p->value); p->value = enc; + p->encoded = 1; } return 0; } diff --git a/lib/reqsign.c b/lib/reqsign.c index d09b938..774f69b 100644 --- a/lib/reqsign.c +++ b/lib/reqsign.c @@ -52,6 +52,7 @@ querysign2(struct ec2_query *req, char *secret) struct pname pn; char *str; char digest[SHA256_DIGEST_SIZE]; + char *signature; size_t siglen; const char *verb; char tsbuf[22]; @@ -60,6 +61,7 @@ querysign2(struct ec2_query *req, char *secret) acc = grecs_txtacc_create(); /* Add default parameters */ + eclat_query_add_param(req, "AWSAccessKeyId", req->access_key); eclat_query_add_param(req, "SignatureMethod", "HmacSHA256"); eclat_query_add_param(req, "SignatureVersion", "2"); @@ -78,11 +80,11 @@ querysign2(struct ec2_query *req, char *secret) qsort(pnames, n, sizeof(pnames[0]), compnames); verb = (req->flags & EC2_QF_POST) ? "POST" : "GET"; - grecs_txtacc_grow(acc, verb, strlen(verb)); + grecs_txtacc_grow_string(acc, verb); grecs_txtacc_grow_char(acc, '\n'); - grecs_txtacc_grow(acc, req->endpoint, strlen(req->endpoint)); + grecs_txtacc_grow_string(acc, req->endpoint); grecs_txtacc_grow_char(acc, '\n'); - grecs_txtacc_grow(acc, req->uri, strlen(req->uri)); + grecs_txtacc_grow_string(acc, req->uri); grecs_txtacc_grow_char(acc, '\n'); /* Append a canonicalized query string */ @@ -95,10 +97,10 @@ querysign2(struct ec2_query *req, char *secret) abort(); if (i != 0) grecs_txtacc_grow_char(acc, '&'); - grecs_txtacc_grow(acc, p->name, strlen(p->name)); + grecs_txtacc_grow_string(acc, p->name); if (p->value) { grecs_txtacc_grow_char(acc, '='); - grecs_txtacc_grow(acc, p->value, strlen(p->value)); + grecs_txtacc_grow_string(acc, p->value); } } grecs_txtacc_grow_char(acc, 0); @@ -107,8 +109,10 @@ querysign2(struct ec2_query *req, char *secret) hmac_sha256(str, strlen(str), secret, strlen(secret), digest); eclat_base64_encode((unsigned char *)digest, sizeof(digest), - (unsigned char**) &req->signature, &siglen); - + (unsigned char**) &signature, &siglen); + eclat_query_add_param_encoded(req, "Signature", signature); + free(signature); + grecs_txtacc_free(acc); free(pnames); @@ -119,10 +123,220 @@ querysign2(struct ec2_query *req, char *secret) */ } +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]; + } +} + +/* Ref. http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + */ static void querysign4(struct ec2_query *req, char *secret) { - abort(); + char **pnames; + struct pname pn; + size_t i, n; + struct grecs_txtacc *acc; + char digest[SHA256_DIGEST_SIZE]; + size_t siglen; + const char *verb; + char tsbuf[22]; + time_t t; + char const *p; + char const *payload; + unsigned char *plhash = NULL; + size_t plsize = 0; + char *string_to_sign; + static char algostr[] = "AWS4-HMAC-SHA256"; + static char termstr[] = "aws4_request"; + char *canonical_req; + char *signed_headers; + 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); + + /* Signed headers */ + grecs_txtacc_grow_string(acc, "host"); + grecs_txtacc_grow_char(acc, 0); + signed_headers = grecs_txtacc_finish(acc, 0); + + eclat_query_add_header(req, "Host", req->endpoint); + if (!(req->flags & EC2_QF_POST)) { + eclat_query_add_param(req, "X-Amz-Algorithm", algostr); + eclat_query_add_param(req, "X-Amz-Date", tsbuf); + eclat_query_add_param(req, "X-Amz-SignedHeaders", + signed_headers); + eclat_query_add_param(req, "X-Amz-Credential", credential); + } + + /* Encode the query */ + eclat_query_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_QF_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 query 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_string(acc, req->endpoint); + 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_QF_POST) + /* FIXME: payload = req->query */; + 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), + &plhash, &plsize); + grecs_txtacc_grow(acc, plhash, plsize); + free(plhash); + + 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, &plsize); + + /* 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, &plsize); + + if (req->flags & EC2_QF_POST) { + /* 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_query_add_header(req, "Authorization", p); + } else { + eclat_query_add_param(req, "X-Amz-Signature", signature); + } + free(signature); + grecs_txtacc_free(acc); + /* Encode the query */ + eclat_query_encode(req); } |