I'm trying to generate the signature in Google Scripts without much luck.
My code is as follows:
function getAwsData(){
var AWS_SECRET = '[AWS_SECRET]';
var expiresDt = Math.floor(Date.now() / 1000) + (60 * 60 * 24);
var path = '/MYFOLDER/ACTIVATION.csv'
var stringToSign = 'GET\n\n\n' + expiresDt + '\n' + path;
var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, AWS_SECRET,stringToSign, Utilities.Charset.UTF_8);
var signature = encodeURIComponent(Utilities.base64Encode(hmac));
var response = UrlFetchApp.fetch("http://s3-ap-southeast-2.amazonaws.com/MYFOLDER/ACTIVATION.csv?AWSAccessKeyId=[AWS_ACCESS_ID]&Expires="+expiresDt+"&Signature="+signature);
var csvData = Utilities.parseCsv(response, ",");
console.log(csvData);
}
Can someone please see where I've gone wrong? I've followed the document here: http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html
It appears that I'm signing the hmac incorrectly.
var hmac = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_1, stringToSign, AWS_SECRET,Utilities.Charset.UTF_8);
Related
I'm using postman for an api request. My question is; I'm getting an error when I made one of the variables random.
For example when I sent request this link;
https://testrest.xxxxxx.com/voucher/purchase/testAUD000/49/1/30000017/1 it is working. But when I change with this;
https://testrest.xxxxxx.com/voucher/purchase/testAUD000/49/1/30000017/{{transactionId}}
I'm getting this error; "You are not authorized to access this resource"
In my collection pre-request script;
var apiKey = pm.collectionVariables.get("API-KEY");
var apiSecret = pm.collectionVariables.get("API-SECRET");
var nonce = _.random(1, 50000000);
var signature = pm.request.method + "\n" + pm.request.url.getPath() + "\n" + nonce + "\n";
var hash = CryptoJS.HmacSHA256(signature, apiSecret).toString();
pm.request.headers.add("AUTHENTICATION: HMAC " + apiKey + ":" + hash + ":" + nonce);
I added this in collection pre-request script or request in pre-request script I'm getting error;
var random = Math.floor(Math.random() * 10);
pm.variables.set('transactionId',random)
I am trying to fetch a jpg image from AWS S3 Bucket using a HTTP GET request in React-Native.
So far I was following these 2 documentations from Amazon :
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
Here is the logic I implemented so far.
When my component render, I am doing the following :
import { Auth } from 'aws-amplify';
import Moment from 'moment';
import sha256 from 'crypto-js/sha256';
var CryptoJS = require("crypto-js");
...
Auth.currentCredentials()
.then(async credentials => {
let awsHost = 'https://myAwsHost.amazonaws.com'
let awsPath = '/public/pathToMyImage.jpg'
let accessKeyId = credentials.accessKeyId
let secretAccessKey = credentials.secretAccessKey
let sessionToken = credentials.sessionToken
let awsDate = Moment().utc().format("YYYYMMDD")
let awsRequestDateTime = Moment().utc().format("YYYYMMDD" + "T" + "HHmmss") + "Z"
let awsRegion = "myRegion"
let awsService = "s3"
let awsRequest = "aws4_request"
let awsAlgorithm = "AWS4-HMAC-SHA256"
let awsCredentialScope = awsDate + "/" + awsRegion + "/" + awsService + "/" + awsRequest
let awsHTTPRequestMethod = "GET"
let awsSignedHeaders = "host;x-amz-content-sha256;x-amz-date;x-amz-security-token"
let awsCanonicalURI = awsHost + awsPath
let awsCanonicalQueryString = ""
// Step 1
let canonicalRequest =
awsHTTPRequestMethod + '\n' +
awsCanonicalURI + '\n' +
awsCanonicalQueryString + '\n' +
"X-Amz-Content-Sha256".toLowerCase() + ":" + sha256("") + "\n" +
"X-Amz-Date".toLowerCase() + ":" + awsRequestDateTime.trim() + "\n" +
"X-Amz-Security-Token".toLowerCase() + ":" + sessionToken + "\n" +
awsSignedHeaders + '\n' +
sha256("")
// Step 2 :
let stringToSign =
awsAlgorithm + "\n" +
awsRequestDateTime + "\n" +
awsCredentialScope + "\n" +
sha256(canonicalRequest)
// Step 3 :
let kSecret = secretAccessKey
let kDate = CryptoJS.HmacSHA256("AWS4" + kSecret, awsDate)
let kRegion = CryptoJS.HmacSHA256(kDate, awsRegion)
let kService = CryptoJS.HmacSHA256(kRegion, awsService)
let kSigning = CryptoJS.HmacSHA256(kService, awsRequest).toString(CryptoJS.enc.Hex)
let awsSignature = CryptoJS.HmacSHA256(awsDerivedSignInKey, stringToSign).toString(CryptoJS.enc.Hex)
let awsAuthorization = awsAlgorithm + " Credential=" + accessKeyId + "/" + awsCredentialScope + ", SignedHeaders=" + awsSignedHeaders + ", Signature=" + awsSignature
// Fetching the image with an HTTP GET request to AWS S3 Buckets
try {
await fetch(
awsCanonicalURI,
{
headers: {
'X-Amz-Security-Token': sessionToken,
'X-Amz-Content-Sha256': sha256("").toString(),
'X-Amz-Date': awsRequestDateTime,
'Authorization': awsAuthorization
},
}
).then(res => {
console.log("HTML status : " + res.status) // Status return : Error 403
});
} catch (error) {
console.error(error);
}
})
}
When I am trying to execute the same GET request with postman it work's and it retrieve the image from AWS S3 Buckets.
The only difference between my request and the one generated by postman is the Signature.
I know that I can fetch images from S3Image component from aws-amplify-react-native but it is not what I am trying to achieve.
Main goal
Finally, I am looking to execute those HTTP GET Request's in order to use them in FastImage from the module react-native-fast-image and use it in order to cache easely images in my react-native application.
If anyone has an answer to my problem or a better alternative to what I am trying to achieve, be my guest!!
If it works in postman, You can get the Axios code sample for that using postman. Checkout the gif below.
I'm trying to create the final step of this example in Flutter and I can't get it right for some reason:
https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
Using their Signing Key + String to sign they get this resulting signature:
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7
Using the exact same key / string to sign I end up with:
fe52b221b5173b501c9863cec59554224072ca34c1c827ec5fb8a257f97637b1
Here is my code:
var testSigninKey =
'c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9';
var testStringToSign = 'AWS4-HMAC-SHA256' + '\n' +
'20150830T123600Z' + '\n' +
'20150830/us-east-1/iam/aws4_request' + '\n' +
'f536975d06c0309214f805bb90ccff089219ecd68b2577efef23edd43b7e1a59';
var hmac1 = Hmac(sha256, utf8.encode(testSigninKey));
var digest1 = hmac1.convert(utf8.encode(testStringToSign));
print(digest1);
print(hex.encode(digestBytes.bytes));
//both print: fe52b221b5173b501c9863cec59554224072ca34c1c827ec5fb8a257f97637b1
The hmac1 calls expects you to use the signing key as is, not to use an encoded version of the hex string representation of it.
You should be able to properly construct the Hmac object with this:
var hmac1 = Hmac(sha256, hex.decode(testSigninKey));
Here is a complete example of my flutter code, referring to the s3 signature creation example:
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
I know it's not perfect but it took me extremely long to figure it out. Maybe it helps someone.
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
class AWS {
var _algorithm = ["X-Amz-Algorithm", "AWS4-HMAC-SHA256"];
final String _awsService = "s3";
final String _aws4Request = "aws4_request";
var _expires = ["X-Amz-Expires", "86400"];
var _signedHeaders = ["X-Amz-SignedHeaders", "host"];
var testbucket = "https://s3.amazonaws.com/examplebucket";
var testfilePath = "/test.txt";
var _testDate = ["X-Amz-Date", "20130524T000000Z"];
var _testDateymd = 20130524;
var _testRegion = "us-east-1";
var _testCredential = [
"X-Amz-Credential",
"AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request"
];
var testSecretkey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
testString() {
var httpMethod = "GET";
var canUri = testfilePath;
var canQueryStr = Uri.encodeQueryComponent(_algorithm[0]) +
"=" +
Uri.encodeQueryComponent(_algorithm[1]) +
"&" +
Uri.encodeQueryComponent(_testCredential[0]) +
"=" +
Uri.encodeQueryComponent(_testCredential[1]) +
"&" +
Uri.encodeQueryComponent(_testDate[0]) +
"=" +
Uri.encodeQueryComponent(_testDate[1]) +
"&" +
Uri.encodeQueryComponent(_expires[0]) +
"=" +
Uri.encodeQueryComponent(_expires[1]) +
"&" +
Uri.encodeQueryComponent(_signedHeaders[0]) +
"=" +
Uri.encodeQueryComponent(_signedHeaders[1]);
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
var _canonicalRequest = httpMethod +
"\n" +
canUri +
"\n" +
canQueryStr +
"\n" +
"host:examplebucket.s3.amazonaws.com" +
"\n" +
"\n" +
"host" +
"\n" +
"UNSIGNED-PAYLOAD";
var bytes = utf8.encode(_canonicalRequest);
var _stringToSign = "AWS4-HMAC-SHA256" +
"\n" +
"20130524T000000Z" +
"\n" +
"20130524/us-east-1/s3/aws4_request" +
"\n" +
sha256.convert(bytes).toString();
print("String to sign: $_stringToSign");
// HEX und HMAC signature
List<int> _dateKey = utf8.encode("AWS4" + testSecretkey);
List<int> _dateMsg = utf8.encode(_testDateymd.toString());
Hmac dateHmac = Hmac(sha256, _dateKey);
Digest dateDigest = dateHmac.convert(_dateMsg);
String _dateRegionKey = dateDigest.toString();
List<int> _dateRegionMsg = utf8.encode(_testRegion.toString());
Hmac dateRegionHmac = Hmac(sha256, hex.decode(_dateRegionKey.toString()));
Digest dateRegionDigest = dateRegionHmac.convert(_dateRegionMsg);
String _dateRegionServiceKey = dateRegionDigest.toString();
List<int> _dateRegionServiceMsg = utf8.encode(_awsService.toString());
Hmac dateRegionServiceHmac =
Hmac(sha256, hex.decode(_dateRegionServiceKey.toString()));
Digest dateRegionServiceDigest =
dateRegionServiceHmac.convert(_dateRegionServiceMsg);
String _signingKey = dateRegionServiceDigest.toString();
List<int> _signingKeyMsg = utf8.encode(_aws4Request.toString());
Hmac _signingKeyHmac = Hmac(sha256, hex.decode(_signingKey.toString()));
Digest _signingKeyDigest = _signingKeyHmac.convert(_signingKeyMsg);
print("signing key: $_signingKeyDigest");
var _signatureKey = _signingKeyDigest.toString();
List<int> _signatureKeyMsg = utf8.encode(_stringToSign);
Hmac _signatureHmac = Hmac(sha256, hex.decode(_signatureKey));
var _signature = _signatureHmac.convert(_signatureKeyMsg);
print("Signature: $_signature");
}
}
I am trying to generate a simple JWT, using ES256 using KMS. Everything looks fine to the naked eye. But I get "Invalid signature" when I test it through jwt.io. The code is quite simple:
public async Task<string> GenerateJwt(object payload)
{
var encodedHeader = Base64Encoder.EncodeBase64Url(JsonSerializer.Serialize(_header));
var encodedPayload = Base64Encoder.EncodeBase64Url(JsonSerializer.Serialize(payload));
var signatureData = Encoding.ASCII.GetBytes(encodedHeader + "." + encodedPayload);
var signature = await _signingService.Sign(signatureData);
var encodedSignature = Base64Encoder.ReplaceSpecialUrlCharacters(Convert.ToBase64String(signature));
return encodedHeader + "." + encodedPayload + "." + encodedSignature;
}
The SigningService looks something like this:
public async Task<byte[]> Sign(byte[] signatureData)
{
using var memoryStream = new MemoryStream(signatureData, 0, signatureData.Length);
var signRequest = new SignRequest()
{
KeyId = _signingKeyId,
Message = memoryStream,
SigningAlgorithm = SigningAlgorithmSpec.ECDSA_SHA_256
};
SignResponse signResponse = await _keyManagementService.SignAsync(signRequest);
return signResponse.Signature.ToArray();
}
_keyManagementService is an IAmazonKeyManagementService from AWSSDK.KeyManagementService 3.5.2.6
In KMS the key is set up like this:
Key Spec: ECC_NIST_P256
Key Usage: Sign and verify
Signing algorithms: ECDSA_SHA_256
Public key in KMS (using localstack)
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEj7Cy/Gbx3jnuPdanuBSjuUmdnr7tQ/BcOytDlFoHWdNA1scc6RwNwqnNbmRE0BmwnlFNDVGxWU5oycTig8p0KQ==
Example on generated output
eyJhbGciOiJFUzI1NiJ9.eyJ0ZXN0MSI6MSwidGVzdDIiOiJ0d28iLCJ0ZXN0MyI6ZmFsc2V9.MEYCIQCiatnRhYGBKgdJj9LECe7mJ4bhhkVTvFSgpVI3Dm14pwIhAOrHAu0vqKvVwdgpAhaU7KOhiIBZdcEOuzfXrdXldCFQ
If it sign it with the built in tools (by just replacing the signatureData row above) with the same input I get it to validate in jwt.io.
var ecdsa = ECDsa.Create();
ecdsa.GenerateKey(ECCurve.NamedCurves.nistP256);
var signatureData = ecdsa.SignData(signatureData, HashAlgorithmName.SHA256);
Any input would be welcome as it feels like I've tested everything...
The problem was that KMS returns the signature in another format:
DER-encoded object as defined by ANS X9.62–2005
While the JWT should in the format R || S according to https://www.rfc-editor.org/rfc/rfc7515#appendix-A.3.1
So what I did as to add a convert method using BouncyCastle and called that before converting it to base64url:
private byte[] ConvertSignature(byte[] signature)
{
var asn1 = Asn1Object.FromByteArray(signature) as DerSequence;
if (asn1 == null)
return Array.Empty<byte>();
if (asn1.Count < 2)
return Array.Empty<byte>();
var r = asn1[0] as DerInteger;
var s = asn1[1] as DerInteger;
if (r == null || s == null)
return Array.Empty<byte>();
return Array.Empty<byte>()
.Concat(r.Value.ToByteArrayUnsigned())
.Concat(s.Value.ToByteArrayUnsigned())
.ToArray();
}
Perhaps I'm going a bridge to far here but heres what I got:
An AWS API Gateway Method that has AWS_IAM set for Authorization.
A Policy that allows access to that Method.
An EC2 Role that has that policy attached to it.
An EC2 Launched with that Role.
I would like to have my NodeJS program (or any language for that matter) on that EC2 to be able to call that API without hardcoding an AccessKey and SecretKey in the code.
I have used this approach to use the aws-sdk to put/get records on S3, and do other AWS functionality (like all the steps I mentioned above), However, invoking an API Gateway seems to be outside the aws-sdk scope.
Calling the API with Wreck (the NPM I use from my HTTP calls in my app) and no headers results in:
{
"message": "Missing Authentication Token"
}
Not a big shock there.
Anything obvious I am missing?
So it appears you need to access your EC2 at http://169.254.169.254/latest/meta-data/iam/security-credentials/Role_Name
As explained here.
Here is my final code including Signing AWS Requests with Signature Version 4:
var Moment = require('moment');
var Wreck = require('wreck');
var Crypto = require('crypto');
function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = Crypto.createHmac('sha256', 'AWS4' + key).update(dateStamp,'utf8').digest();
var kRegion = Crypto.createHmac('sha256', kDate).update(regionName, 'utf8').digest();
var kService = Crypto.createHmac('sha256', kRegion).update(serviceName, 'utf8').digest();
var kSigning = Crypto.createHmac('sha256', kService).update('aws4_request', 'utf8').digest();
return kSigning;
}
var assumed_role = 'MY_ROLE';
Wreck.get('http://169.254.169.254/latest/meta-data/iam/security-credentials/' + assumed_role, function(err, res, payload) {
var payload_obj = JSON.parse(payload.toString());
var access_key = payload_obj.AccessKeyId;
var secret_key = payload_obj.SecretAccessKey;
var token = payload_obj.Token;
var payload = {}
payload.email = 'devin.stewart#example.com';
payload.first_name = 'Devin';
payload.last_name = 'Stewart';
payload.full_name = 'Devin Stewart';
var request_parameters = JSON.stringify(payload);
var method = 'POST';
var api_id = 'MY_API_ID'
var service = 'execute-api';
var region = 'us-east-1';
var api_path = '/production/people';
var host = api_id + '.' + service + '.' + region + '.amazonaws.com';
var endpoint = 'https://' + host + api_path;
var content_type = 'application/json';
var t = Moment.utc()
var amz_date = t.format('YYYYMMDD[T]HHmmss[Z]');
var date_stamp = t.format('YYYYMMDD'); // Date w/o time, used in credential scope
var canonical_querystring = '';
var canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n' + 'x-amz-security-token:' + token + '\n';
var signed_headers = 'content-type;host;x-amz-date;x-amz-security-token';
var payload_hash = Crypto.createHash('sha256').update(request_parameters).digest('hex');
var canonical_request = method + '\n' + api_path + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash;
var algorithm = 'AWS4-HMAC-SHA256';
var credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request';
var string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + Crypto.createHash('sha256').update(canonical_request).digest('hex');
var signing_key = getSignatureKey(secret_key, date_stamp, region, service);
var signature = Crypto.createHmac('sha256', signing_key).update(string_to_sign, 'utf8').digest('hex');
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature;
var headers = {
'Content-Type':content_type,
'X-Amz-Date':amz_date,
'X-Amz-Security-Token':token,
'Authorization':authorization_header
};
var options = {headers: headers, payload: request_parameters};
Wreck.post(endpoint, options, function (err, res, payload) {
if (err) {
console.log(err.data.payload.toString());
} else {
console.log(payload.toString());
}
});
});