0

We are trying to use AWS Transcribe service to convert voice to text. As first step we are trying to authenticate to AWS using the documentation they have provided. Whatever we do we end up with the below error.

{"__type":"InvalidSignatureException","message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}

We are able to successfully call transcribe service from postman and named credentials doesn't work.

Below is our apex code:

public class S3Test1 {

   // public static HttpRequest createAWSRequest(String method, String service, String host, String region, String endpoint, String contentType, String requestJson) {
    public static void createAWSRequest() { 
        String method = 'POST';
        String service = 'transcribe';
        String host = 'transcribe.us-west-2.amazonaws.com';
        String region = 'us-west-2';
        String endpoint = 'https://transcribe.us-west-2.amazonaws.com/';
        String contentType = 'application/x-amz-json-1.1';
        String requestJson ='{"LanguageCode": "en-US","Media": {"MediaFileUri": "S3 audio file link"},"MediaFormat": "mp4","MediaSampleRateHertz": 44100,"TranscriptionJobName": "SampleRestJob7"}';
        String amzDate = system.Now().format('yyyyMMdd')+ 'T' + system.Now().formatGMT('HHmmss') + 'Z';
        String dateStamp = system.Now().format('yyyyMMdd');
        String accessKey = 'our access key';
        String secretKey = 'our secret key';

        String canonical_headers = 'content-type:' + contentType + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amzdate + '\n'+'x-amz-target:'+'Transcribe.StartTranscriptionJob' + '\n';
        String signedHeaders = 'content-type;host;x-amz-date;x-amz-target';
        Blob payloadHash = Crypto.generateDigest('SHA-256', Blob.valueOf(requestJson));
        String hexpayload = EncodingUtil.convertToHex(payloadHash);

        String canonicalRequest = method +'\n'+ canonical_headers + '\n' + signedHeaders + '\n' + hexpayload; 
      //   String hexcanonicalrequest = EncodingUtil.convertToHex(canonicalRequest);

        //TASK 2: CREATE THE STRING TO SIGN
         String algorithm = 'AWS4-HMAC-SHA256';
        String credentialScope = dateStamp + '/' + region + '/' + service + '/' + 'aws4_request';
        String stringToSign = algorithm + '\n' +  amzDate + '\n' +  credentialScope + '\n' +  EncodingUtil.convertToHex(Crypto.generateDigest('SHA-256', Blob.valueOf(canonicalRequest)));
        system.debug('stringToSign' + stringToSign);
         Blob signingkey = getSignatureKey(secretKey, dateStamp, region, service);
         Blob signature = Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingkey);
        String hexSignature = EncodingUtil.convertToHex(signature);

        //TASK 4: ADD SIGNING INFORMATION TO THE REQUEST
        String authorizationHeader = algorithm + ' ' + 'Credential=' + accessKey + '/' + credentialScope + ', ' +  'SignedHeaders=' + signedHeaders + ', ' + 'Signature=' + hexSignature;
         HttpRequest httpReq = new HttpRequest();
        httpReq.setMethod(method);
        httpReq.setEndpoint(endpoint);
        httpReq.setBody(requestJson);
        httpReq.setHeader('Content-Type', contentType);
        httpReq.setHeader('host', host);
        httpReq.setHeader('x-amz-date', amzDate);
        httpReq.setHeader('x-amz-target', 'Transcribe.StartTranscriptionJob');
        httpReq.setHeader('Authorization', authorizationHeader);
          HTTPResponse response = new HTTPResponse();
       Http http = new Http();   
       response = http.send(httpReq);
        system.debug(response);
        system.debug(response.getbody());

        if(response.getStatusCode() == 200){
            System.debug('Request Successful');
            System.debug(response.getBody());
        AWSTranscribeResponseWrapper resWrapper = (AWSTranscribeResponseWrapper)JSON.deserialize(response.getBody(), AWSTranscribeResponseWrapper.class);
        System.debug(resWrapper);
       // System.debug(resWrapper.Transcript.TranscriptFileUri);
        }

    }

    public Static Blob sign(Blob key, String msg) {
        return Crypto.generateMac('hmacSHA256', Blob.valueof(EncodingUtil.urlEncode(msg, 'UTF-8')), key); // hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
    }

    public static Blob getSignatureKey(String key, String dateStamp, String regionName, String serviceName) {
      Blob kDate = sign(Blob.valueOf('AWS4' + key), dateStamp);
        Blob kRegion = sign(kDate, regionName);
        Blob kService = sign(kRegion, serviceName);
        Blob kSigning = sign(kService, 'aws4_request');
        return kSigning;
    }
}

Any Inputs on what is wrong with the code would be super helpful.

9
  • 3
    Does this answer your question? Named credential AWS Signature V4 - incorrect signature
    – identigral
    Commented Apr 16, 2020 at 19:59
  • No.Already checked that, It did not work.The link is to authenticate S3 whereas i am trying to authenticate transcribe service. there are subtle differences.
    – RedDevil
    Commented Apr 16, 2020 at 20:00
  • @sfdcfox, it is check the method getSignatureKey and sign at the end. Am i missing something?
    – RedDevil
    Commented Apr 16, 2020 at 20:09
  • Sorry, I see that now. I'm comparing your code to mine to see where the problem is. I know mine works, we use it currently.
    – sfdcfox
    Commented Apr 16, 2020 at 20:11
  • Here's the link again, just realized I deleted it. gist.github.com/brianmfear/92cf05807ac4becbd21f
    – sfdcfox
    Commented Apr 16, 2020 at 20:13

2 Answers 2

0

Thanks to Sfdcfox,was able to find the root cause, posting answer for benefit of community

From AWS documentation below is the format of canonical request

CanonicalRequest =
  HTTPRequestMethod + '\n' +
  CanonicalURI + '\n' +
  CanonicalQueryString + '\n' +
  CanonicalHeaders + '\n' +
  SignedHeaders + '\n' +
  HexEncode(Hash(RequestPayload))

In our case our request was in the body and we did not have query string so omitted CanonicalURI and CanonicalQueryString because of which we ran into signature mis-match error.

Guess when we use SHA 256 algorithm to generate digest, every character matters which is why this error happened.

  1. if the URI is blank just use a forward slash '/'
  2. if query string is null just instantiate a empty query string as part of the canonical request.
  3. For AWS transcribe the content type has to be application/x-amz-json-1.1 both in the header and part of canonical headers.
0

Create a name credential AWS_Transcribe with AWS S4 signature version and use the following code-

public static void createAWSCallTranscribeRequest() { 
        String method = 'POST';       
        String endpoint = 'callout:AWS_Transcribe/';
        String contentType = 'application/x-amz-json-1.1';
        String requestJson ='{"LanguageCode": "en-US","Media": {"MediaFileUri": "url"},"MediaFormat": "wav","TranscriptionJobName": "TestTranscribe"}';
        
        HttpRequest httpReq = new HttpRequest();
        httpReq.setMethod(method);
        httpReq.setEndpoint(endpoint);
        httpReq.setBody(requestJson);
        httpReq.setHeader('Content-Type', contentType);
        
        httpReq.setHeader('x-amz-target', 'Transcribe.StartTranscriptionJob');
        
       HTTPResponse response = new HTTPResponse();
       Http http = new Http();   
       response = http.send(httpReq);
        system.debug(response);
        system.debug(response.getbody());

        if(response.getStatusCode() == 200){
            System.debug('Request Successful');
            System.debug(response.getBody());
       
        }

    }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .