Apex AWS – AWS Cloudfront Signed URL from Apex

We are trying to access S3 resources via Cloudfront inside a VF page in Salesforce.

The S3 bucket, folder & files are all marked private and the access is granted only via Cloudfront.

We already tried generating the Signed url using the approach discussed in this thread in SFSE and also this option in SF Dev Forum

With both the options, we are getting 403 AccessDenied errors.

here's the current code we have in place.

public static String GetCFsignedURL() {
    //Private Key in RSA
    String cloudfrontPrivateKey1 = '.....';

    //Private Key in PKCS8
    String cloudfrontPrivateKey2 = '....';        


    String keyPairId = '...';
    String secret    = '...';

    Datetime dt      = Datetime.now();
    Long longTime    = dt.getTime();
    Long expiryLong  = (longTime / 1000) + 3600;
    String expiry    = String.valueOf(expiryLong);

    String strpolicy = '{"Statement": [{"Resource": "https://prefix.cloudfront.net/s3folder/s3File","Condition": {"DateLessThan": {"AWS:EpochTime": ' + expiry + '}}}]}';

    String policyEnc = EncodingUtil.base64Encode(Blob.valueOf(strpolicy));

    policyEnc = policyEnc.replace('+', '-').replace('=', '_').replace('/', '~');

    //Blob mac         = Crypto.Sign('RSA',Blob.valueOf(policyEnc), EncodingUtil.base64Decode(cloudfrontPrivateKey1));

    Blob mac = Crypto.generateMac('hmacSHA1', Blob.valueOf(rPolicy), Blob.valueOf(cloudfrontPrivateKey1));

    String policySign = EncodingUtil.base64Encode(mac);

    String finalURL = 'https://prefix.cloudfront.net/s3folder/s3File?Expires=' + expiry + '&Signature=' + policySign + '&Key-Pair-Id=' + keyPairId;

    return finalURL;
}

Note – When I generate the cloudfront signed url from a .NET or Java client using the same private keys & access key pair id, it perfectly works fine. Its failing only with the signed url generated in Salesforce.

if anyone got any luck with generating cloudfront signed url within apex, please share your expertise..

Best Answer

You may want to implement the string replacement code found in both of those answers you referenced. I've run into similar issues generating presigned URLs for S3 and learned the (very) hard way about URL escaping reserved characters in the policy. In your code, you may want to try something like this:

...
String policyEnc = EncodingUtil.base64Encode(Blob.valueOf(strpolicy));
policyEnc = policyEnc.replace('+','-').replace('=','_').replace('/','~');
...

Other languages (eg., Python in my case or .NET/Java in your case) seem to do a better job with this type of URL generation, but I've spent countless hours trying to debug this stuff in Apex in the past. Hope that helps!

Edit: just to be clear, the reason this should help (if not solve the issue) is because you're generating a presigned URL in the end, and that URL has to be properly formatted. If your base64-encoded policy includes characters normally found in URIs/URLs (eg., +, =, or /) it will fail.

Related Topic