authentication - Which headers do I have to pass on an JavaScript S3 AWS request using signature v4 and assumed role credentials? -
i'm trying make rest connection amazon s3 , i'm struggling implement signature v4 process. know, amazon provides javasscript sdk, need work myself.
to test code, i'm using aws testsuite , code returns expected results. however, real life attempts fail because of signature not match error.
the issue me is, examples don't include headers except host , date kind of straightforward. i'm having pass "full monty" including access tokens , i'm sure there problem headers.
here i'm doing on click of button:
function simples3scenario(my_storage) { return new rsvp.queue() .push(function () { return my_storage.alldocs(); }) .push(function (my_alldocs_response) { console.log("initial alldocs") console.log(my_alldocs_response); }) ... }; alldocs run this:
s3storage.prototype.buildquery = function (param, options) { var context = this, url = this._base_url, url_path = this._bucket + "/", headers, requesttext; return new rsvp.queue() .push(function () { return sigv4_custom.makesignedrequest( "get", url + url_path, '', {"max-key": 100, "prefix": "sample_folder"}, {}, context._region, "s3", "aws4-hmac-sha256", context._secret_access_key, context._access_key, context._session_token ); }) .push(function (e) { console.log("yupi"); console.log(e); }) ... which enters request signature handler, based on (not working signature v4) amazon example | code: sigv4.js:
(sorry, not shortened yet, scroll down entry point, work up)
var sigv4_custom = new function(){ this.createcanonicalrequest = function(){}; this.createstringtosign = function(){}; this.generatesignatureandsign = function(){}; this.makesignedrequest = function(){}; } // trim polyfill string.prototype.trim = string.prototype.trim || function () { var start = -1, end = this.length; while( this.charcodeat(--end) < 33 ); while( this.charcodeat(++start) < 33 ); return this.slice( start, end + 1 ); }; // =============== generator funtions ================= // generate query parameter string canonical request function generatecanonicalquerystring(my_parameter_dict) { var canonical_query_string = '', encoded_parameter_dict = {}, key_list = [], key, value, i, i_len; // set , encode (key in my_parameter_dict) { if (my_parameter_dict.hasownproperty(key)) { value = my_parameter_dict[key]; if (typeof value === "object") { encoded_parameter_dict[encodeuricomponent(key)] = encodeuricomponent(value[0]) + '&'; // append each additional value query parameter (i = 0, i_len = value.length; < i_len; += 1) { encoded_parameter_dict[encodeuricomponent(key)] += encodeuricomponent(key) + '=' + encodeuricomponent(value[i]) +'&'; } encoded_parameter_dict[encodeuricomponent(key)] = encoded_parameter_dict[encodeuricomponent(key)].slice(0,-1); } else { encoded_parameter_dict[encodeuricomponent(key)] = encodeuricomponent(value); } } } // fill key_list (key in encoded_parameter_dict) { if (encoded_parameter_dict.hasownproperty(key)) { key_list.push(key); } } key_list.sort(); (i = 0, i_len = key_list.length; < i_len; += 1) { canonical_query_string += key_list[i] + "=" + encoded_parameter_dict[key_list[i]] + "&"; } return canonical_query_string.slice(0, -1); } // generate canonical header string function generatecanonicalheaderstring(my_header_dict, is_signature) { var canonical_header_string = '', encoded_header_dict = {}, header_list = [], header, value, trimmed_value, header_lowercase, i, i_len, separator, connector, format, cutoff; if (is_signature) { cutoff = -1; separator = ":"; connector = "\n"; format = function (my_value) { return my_value.tolowercase(); } } else { cutoff = -1; separator = "="; connector = "&"; format = function (my_value) { return my_value; } } // take header keys , put in array, sort , build (header in my_header_dict) { if (my_header_dict.hasownproperty(header)) { header_lowercase = format(header); value = my_header_dict[header]; trimmed_value = undefined; //xxx there not strings in headers... if (value.trim) { trimmed_value = value.trim(); } if (encoded_header_dict[header_lowercase] === undefined) { encoded_header_dict[header_lowercase] = trimmed_value || value; header_list.push(header_lowercase); } else { encoded_header_dict[header_lowercase] += "," + trimmed_value || value; } } } header_list.sort(); (i = 0, i_len = header_list.length; < i_len; += 1) { canonical_header_string += header_list[i] + separator + encoded_header_dict[header_list[i]] + connector; } canonical_header_string = canonical_header_string.slice(0, cutoff); return canonical_header_string; } // generate signed header string function generatesignedheaderstring(my_header_dict) { var signed_header_string = '', header_list = [], header, header_lowercase, i, i_len; (header in my_header_dict) { if (my_header_dict.hasownproperty(header)) { header_list.push(header.tolowercase()); } } header_list.sort(); (i = 0, i_len = header_list.length; < i_len; += 1) { signed_header_string += header_list[i] + ';'; } return signed_header_string.slice(0, -1); } // returns timestamp in yyyymmdd't'hhmmss'z' format, sigv4 calls function generatetimestamp() { var date = new date(); function bump(my_time_parameter) { if (my_time_parameter.length === 1) { return '0' + my_time_parameter; } return my_time_parameter; } // assemble date, bump single digits doubles // validation: return "20130524t000000z"; return date.getutcfullyear() + bump((date.getutcmonth()+1).tostring()) + // month bump(date.getutcdate().tostring()) + // day 't' + bump(date.getutchours().tostring()) + // hour bump(date.getutcminutes().tostring()) + // minute bump(date.getutcseconds().tostring()) + // second 'z'; } // generate credential scope function generatecredentialscope(my_timestamp, my_region, my_service) { return my_timestamp.substring(0, 8) + "/" + my_region + "/" + my_service + "/" + "aws4_request"; } // ================== core methods ================== sigv4_custom.createstringtosign = function (request_time, credential_scope, canonical_request, signing_algorithm) { // step 1: designate algorithm (for sigv4 sha256) // step 2: designate requestdate (already done, passed function) // step 3: create credentialscope (already done, passed funtion) return signing_algorithm + '\n' + request_time + '\n' + credential_scope + '\n' + canonical_request; } sigv4_custom.generatesignatureandsign = function(secret, request_time, region, service, string_to_sign) { var datestamp = request_time.substring(0, 8), hash_date, hash_region, hash_service, hash_signing, request = 'aws4_request'; hash_date = cryptojs.hmacsha256(datestamp, 'aws4' + secret, {asbytes: true}); hash_region = cryptojs.hmacsha256(region, hash_date, {asbytes: true}); hash_service = cryptojs.hmacsha256(service, hash_region, {asbytes: true}); hash_signing = cryptojs.hmacsha256(request, hash_service, {asbytes: true}); // sign , return return cryptojs.hmacsha256(string_to_sign, hash_signing) .tostring(cryptojs.enc.hex); } sigv4_custom.createcanonicalrequest = function(method, pathname, parameter_dict, header_dict, hashed_payload) { var canonical_request = "", http_request_method = "", canonical_url = "", canonical_query_string = "", canonical_header_string = "", signed_header_string = "", payload; // step 1: start http request method http_request_method = method; // step 2: add canonicalurl parameter canonical_url = pathname; // step 3: add canonicalquerystring parameter canonical_query_string = generatecanonicalquerystring(parameter_dict); // step 4: add canonicalheaders parameter canonical_header_string = generatecanonicalheaderstring(header_dict, true); // step 5: add signedheaders parameter signed_header_string = generatesignedheaderstring(header_dict); // step 6: add hashed payload payload = hashed_payload; // step 7: string canonicalrequest steps 1 through 6 canonical_request += http_request_method + '\n' + canonical_url + '\n' + canonical_query_string + '\n' + canonical_header_string + '\n\n' + signed_header_string + '\n' + payload; return canonical_request; }; // =================== entry point =================== // call function make sigv4 rest api call. sigv4_custom.makesignedrequest = function (method, path, payload, parameter_dict, header_dict, region, service, signing_algorithm, secret_access_key, access_key_id, security_token) { var link = document.createelement("a"), request_time = generatetimestamp(), scope = generatecredentialscope(request_time, region, service), authorization_string, canonical_request, string_to_sign, request_uri, signature; // link location object, set href use properties link.href = path; // set headers, generate canonical_request // payload must hashed here, may vary , hash required // canonization , header. empty payloads hash empty "". header_dict['x-amz-content-sha256'] = cryptojs.sha256(payload) .tostring(cryptojs.enc.hex); header_dict['host'] = link.hostname; header_dict['x-amz-date'] = request_time; header_dict['x-amz-credential'] = access_key_id + "/" + scope; header_dict['x-amz-security-token'] = security_token; header_dict['x-amz-algorithm'] = signing_algorithm; header_dict['x-amz-expires'] = 86400; // task 1: compute canonical request canonical_request = cryptojs.sha256( this.createcanonicalrequest( method, link.pathname, parameter_dict, header_dict, header_dict['x-amz-content-sha256'] ) ) .tostring(cryptojs.enc.hex); // delete host because in headers (needs in both // create canonicalization) delete parameter_dict['host']; // task 2: create string sign using encoded canonical request string_to_sign = this.createstringtosign( request_time, scope, canonical_request, signing_algorithm ); // task 3: create signature using signing key , string sign signature = this.generatesignatureandsign( secret_access_key, request_time, region, service, string_to_sign ); // task 4: build authorization header header_dict['authorization'] = header_dict['x-amz-algorithm'] + " credential=" + header_dict['x-amz-credential'] + "," + "signedheaders=" + generatesignedheaderstring(header_dict) + "," + "signature=" + signature; // task 5: make request (through jio) return new rsvp.queue() .push(function () { return jio.util.ajax({ "method": method, "url": path, "headers": header_dict }); }); } so when run amazon samples 2 headers through this, correct signature , authorization header @ end. believe i'm doing wrong headers, headers should go signature, can't find online , stuck on few days now.
question:
maybe has idea looking @ how i'm handling headers, why requests aws fail due non-matching signature?
thanks!
edit:
have used amazon example page deriving signatures. if run sample inputs through createsignatureandsign method, can produce same outputs. signature correct secret correct.
Comments
Post a Comment