How to create a signed AWS API request and calculate the signature?

I need to call AWS APIs and I can't do that unless I create the Authorization Header needed. I was able to create a custom action to create the Authorization Header but I can't figure out how to calculate the correct signature that is required. See amazon doc on how to calculate the signature (https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html).

The API Call is a GET request to check the balance of a gift card from the following url (https://api.sitewatch.cloud/v1/prepaid/info/[ppdcode]).

Return Value - String
Define Arguments - [ppdcode] (String)
Dependencies - crypto: ^3.0.1

The following is the error I am receiving:

"error": "Request failed with status code 403"

"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.\n\nThe Canonical String for this request should have been\n'GET\n/v1/prepaid/info/02JHW3N\n\ncontent-type:application/json\nhost:api.sitewatch.cloud\nx-amz-date:20231127T144827Z\nx-api-key:LoAQwCz4JpuP2q6JIsFS4Em12Wpqms36BA4Lfxce\n\ncontent-type;host;x-amz-date;x-api-key\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'\n\nThe String-to-Sign should have been\n'AWS4-HMAC-SHA256\n20231127T144827Z\n20231127/us-east-1/execute-api/aws4_request\n7642b67823887d3dc1d85b4d157ec28ee1320761a2478590dd0005cc40636892'\n"

The below code is what I have currently....(I removed my keys though for this example)

import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';

Future<String?> awsSignature(String? ppdcode) async {
  try {
    final String accessKey = 'YOUR_ACCESS_KEY';
    final String secretKey = 'YOUR_SECRET_KEY';
    final String service = 'execute-api';
    final String region = 'us-east-1';
    final String httpMethod = 'GET';
    final String canonicalUri = '/v1/prepaid/info/$ppdcode';
    final String canonicalQuery = '';
    final String payload = '';

    final DateTime now = DateTime.now().toUtc();
    final String amzDate = DateFormat('yyyyMMddTHHmmssZ').format(now);
    final String dateStamp = DateFormat('yyyyMMdd').format(now);

    final List<int> emptyPayloadHash =
        sha256.convert(utf8.encode(payload)).bytes;
    final String canonicalRequest = buildCanonicalRequest(
        httpMethod, canonicalUri, canonicalQuery, emptyPayloadHash, amzDate);
    final String stringToSign = buildStringToSign(
        amzDate, dateStamp, region, service, canonicalRequest);

    final List<int> signingKey =
        deriveSigningKey(dateStamp, region, service, secretKey);
    final List<int> signature = calculateSignature(stringToSign, signingKey);

    final String authorizationHeader =
        'AWS4-HMAC-SHA256 Credential=$accessKey/$dateStamp/$region/$service/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-api-key, Signature=${base64.encode(signature)}';

    return authorizationHeader;
  } catch (error) {
    print('Error generating AWS signature: $error');
    return null;
  }
}

String buildCanonicalRequest(String method, String uri, String query,
    List<int> payloadHash, String amzDate) {
  final canonicalHeaders =
      'content-type:application/json\nhost:api.sitewatch.cloud\nx-amz-date:$amzDate\nx-api-key:YOUR_API_KEY\n';
  final signedHeaders = 'content-type;host;x-amz-date;x-api-key';
  final canonicalRequest =
      '$method\n$uri\n$query\n$canonicalHeaders\n$signedHeaders\n${base64.encode(payloadHash)}';
  return canonicalRequest;
}

String buildStringToSign(String amzDate, String dateStamp, String region,
    String service, String canonicalRequest) {
  final stringToSign =
      'AWS4-HMAC-SHA256\n$amzDate\n$dateStamp/$region/$service/aws4_request\n${sha256.convert(utf8.encode(canonicalRequest)).toString()}';
  return stringToSign;
}

List<int> deriveSigningKey(
    String dateStamp, String region, String service, String secretKey) {
  final List<int> signingKey = Hmac(sha256, utf8.encode('AWS4$secretKey'))
      .convert(utf8.encode(dateStamp))
      .bytes;
  return Hmac(sha256, signingKey).convert(utf8.encode(region)).bytes;
}

List<int> calculateSignature(String stringToSign, List<int> signingKey) {
  final List<int> signature =
      Hmac(sha256, signingKey).convert(utf8.encode(stringToSign)).bytes;
  return signature;
}

Or if someone knows how to write this code so I don't have to write a custom action for every single different API call that I need to complete as this is just one of many.

4 replies