+ Start a Discussion
John IssaJohn Issa 

Connect to AWS S3 from APEX using AWS Signature Version 4r

String accessKey = 'ACCESSKEY';
            String secretKey = 'SECRETKEY';
            String bucketname = 'BUCKETNAME';
            String host = 's3-us-west-1.amazonaws.com';
            String filename = 'test1.json';
            String hashAlgo = 'HmacSHA256';
            String url = 'https://' + bucketname + '.' + host + '/' + filename;
            Date currentDate = date.today();
            DateTime currentDateRaw = DateTime.now();
            String currentDateOnly = DateTime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
            String regionName = 'us-west-1';
            String serviceName = 's3';
            String strBody = '';

            ////////////////////////////////////
            // 1 - CANONICAL REQUEST
            ////////////////////////////////////

            String strCanonicalRequest = '';
            strCanonicalRequest+='PUT\n';
            strCanonicalRequest+='\n';
            strCanonicalRequest+='content-type:application/x-www-form-urlencoded; charset=utf-8\n';
            strCanonicalRequest+='host:s3-us-west-1.amazonaws.com\n';
            strCanonicalRequest+='x-amz-date:' + currentDate + '\n';
            strCanonicalRequest+='\n';
            strCanonicalRequest+='content-type;host;x-amz-date';
            String strPayloadHash = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(strBody))); // Payload
            strCanonicalRequest+= '\n' + strPayloadHash.toLowerCase();

            ////////////////////////////////////
            // 2 - STRING TO SIGN
            ////////////////////////////////////

            String strStringToSign = '';
            strStringToSign+='AWS4-HMAC-SHA256\n';
            strStringToSign+=currentDateRaw + '\n';
            strStringToSign+=currentDateOnly + '/' + regionName + '/' + serviceName + '/aws4_request';
            String strCanonicalHash = EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(strCanonicalRequest))); // Payload
            strStringToSign+= '\n' + strCanonicalHash.toLowerCase();

            ////////////////////////////////////
            // 3 - SIGNATURE
            ////////////////////////////////////

            String kSecret = 'AWS4' + secretKey;
            Blob kDate = Crypto.generateMac(hashAlgo, Blob.valueOf(currentDateOnly), Blob.valueOf(kSecret));
            Blob kRegion = Crypto.generateMac(hashAlgo, Blob.valueOf(regionName), kDate);
            Blob kService = Crypto.generateMac(hashAlgo, Blob.valueOf(serviceName), kRegion);
            Blob kSigning = Crypto.generateMac(hashAlgo, Blob.valueOf('aws4_request'), kService);
            String strSignature = EncodingUtil.convertToHex(Crypto.generateMac(hashAlgo, Blob.valueOf(strStringToSign), kSigning));
            strSignature = strSignature.toLowerCase();

            ////////////////////////////////////
            // 4 - AUTHORIZATION HEADER
            ////////////////////////////////////

            String strAuthorizationHeader = 'AWS4-HMAC-SHA256 ';
            strAuthorizationHeader+= 'Credential=' + accessKey + '/' + currentDateOnly + '/' + regionName + '/' + serviceName + '/aws4_request, ';
            strAuthorizationHeader+= 'SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, ';
            strAuthorizationHeader+= 'Signature=' + strSignature;


            // PUT
            Http http = new Http();
            HttpRequest req = new HttpRequest();
            req.setEndPoint(url);
			req.setMethod('PUT');
			req.setBody(body);
			req.setHeader('ACL', 'public-read-write');
            req.setHeader('Authorization', strAuthorizationHeader);
			req.setHeader('Date', currentDateOnly);
			req.setHeader('x-amz-content-sha256', 'UNSIGNED-PAYLOAD');
            HttpResponse response = http.send(req);

            if (response.getStatusCode() != 200) {
                System.debug('The status code returned was not expected: ' +
                        response.getStatusCode() + ' ' + response.getStatus());
                System.debug('Auth: ' + strAuthorizationHeader);

            else {

                Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
                System.debug('This is working');
                System.debug(response.getBody());
                String output =  (String)results.get('result');


            }

Hello,

I'm in the process of connecting to S3 out of APEX and I'm receiving the following error message when executing an anonymous request:

The authorization header is malformed; the authorization header requires three components: Credential, SignedHeaders, and Signature.

Example of Signature:
Auth: AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/Tue, 09 Jul 2019 07:54:14 GMT/us-west-1/s3/aws4_request, SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date, Signature=fe5f80f77d5fa3beca038a248ff027d04453421fe2855ddc963176630326f1024

When I look at the authorization signature, it looks to be setup correctly.  I"m able to add that signature to the body of my postman request and I get a successful response but when I add it as a header, I receive a 403 message.

Has any one experienced something similar?  If so, any assistance woiuld be greatly appreciated.  TY!

 
surbhi jain 72surbhi jain 72
Hi,

Here's a gist that shows the basic framework that I developed. To actually implement a sub-service using this framework, you simply need a couple of extra steps, outlined here:
 
public class AWSS3_GetService extends AWS { public override void init() { endpoint = new Url('https://s3.amazonaws.com/'); resource = '/'; region = 'us-east-1'; service = 's3'; accessKey = 'my-key-here'; method = HttpMethod.XGET; // You can specify "payload" here if a body is required // payload = Blob.valueof('some-text'); // This method helps prevent leaking secret key, // as it is never serialized createSigningKey('my-secret-key-here'); } public String[] getBuckets() { HttpResponse response = sendRequest(); String[] results = new String[0]; // Read response XML; if we get this far, no exception happened // This code was omitted for brevity return results; } }

Regards - AWS Training in pune (https://writeupcafe.com/community/new-terminologies-in-aws/?snax_post_submission=success)