0

I'm attempting to implement AWS Signature Version 4 signing process in Dart for accessing secured resources in an S3 bucket. I've set up the signing process according to the guidelines in the AWS documentation. However, I keep receiving the "The request signature we calculated does not match the signature you provided. Check your key and signing method" error.

Here is my current implementation:

  Options getOptions(fedUser, String url) {
    final time = formatAmzDate();
    final dateStamp = getDateStamp();
    final signature = getSignature(
        time,
        url,
        dateStamp,
        fedUser['credentials']['sessionToken'],
        fedUser['credentials']['secretAccessKey']);

    final headers = {
      'Authorization':
          'AWS4-HMAC-SHA256 Credential=${fedUser['credentials']['accessKeyId']}/$dateStamp/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=$signature',
      'X-Amz-Content-Sha256':
          'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
      'X-Amz-Date': time,
      'X-Amz-Security-Token': fedUser['credentials']['sessionToken'],
    };

    return Options(
      headers: headers,
    );
  }

  String formatAmzDate() {
    var now = DateTime.now().toUtc();
    var formatter = DateFormat('yyyyMMdd\'T\'HHmmss\'Z\'');
    return formatter.format(now);
  }

  String getDateStamp() {
    var now = DateTime.now().toUtc();
    var formatter = DateFormat('yyyyMMdd');
    return formatter.format(now);
  }

  String getSignature(String time, String newUrl, String dateStamp,
      String token, String secretAccessKey) {
    final hostName = Uri.parse(newUrl).host;
    final pathName = Uri.parse(newUrl).path;
    final canonicalReq = getCanonicalReq(pathName, time, hostName, token);
    final stringToSign = getStringToSign(time, dateStamp, canonicalReq);
    final signingKey =
        getSignatureKey(secretAccessKey, dateStamp, region, 's3');
    final signature = Hmac(sha256, signingKey) // pass bytes directly here
        .convert(utf8.encode(stringToSign))
        .toString();
    return signature;
  }

  String getCanonicalReq(String path, String time, String url, String token) {
    const hashedPayload =
        'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
    final canonicalReq = 'GET\n'
        '$path\n'
        '\n'
        'host:$url\n'
        'x-amz-content-sha256:$hashedPayload\n'
        'x-amz-date:$time\n'
        'x-amz-security-token:$token\n'
        '\n'
        'host;x-amz-content-sha256;x-amz-date;x-amz-security-token\n'
        '$hashedPayload';
    final bytes = utf8.encode(canonicalReq);
    final digest = sha256.convert(bytes);
    return digest.toString();
  }

  String getStringToSign(String time, String dateStamp, String canonicalReq) {
    var stringToSign = 'AWS4-HMAC-SHA256\n'
        '$time\n'
        '$dateStamp/$region/s3/aws4_request\n'
        '$canonicalReq';
    print('stringToSign $stringToSign');
    return stringToSign;
  }

  List<int> getSignatureKey(
      String key, String dateStamp, String regionName, String serviceName) {
    var kDate = _getHmacSha256("AWS4$key", dateStamp);
    var kRegion = _getHmacSha256(String.fromCharCodes(kDate), regionName);
    var kService = _getHmacSha256(String.fromCharCodes(kRegion), serviceName);
    var kSigning =
        _getHmacSha256(String.fromCharCodes(kService), "aws4_request");
    return kSigning;
  }

  List<int> _getHmacSha256(String key, String message) {
    var keyBytes = utf8.encode(key);
    var messageBytes = utf8.encode(message);
    var hmac = Hmac(sha256, keyBytes);
    return hmac.convert(messageBytes).bytes;
  }

  requestFile(Options options, url) async {
    Response response = await dio.get(url, options: options);
    if (response.statusCode == 200) {
      print('response: $response');
      return response.data;
    } else {
      print('Request failed with status: ${response.statusCode}');
      return false;
    }
  }

For a brief overview, getOptions function prepares the headers required for the request, getSignature function creates the signature string by using different functions like getCanonicalReq and getStringToSign. The getSignatureKey function creates the derived signing key using the Hmac function. Finally, requestFile sends the request to the server.

I'm certain that the problem lies somewhere in the signing process, as the AWS Key and Secret Key I've obtained are correct.

It should be pretty straightforward as I have implemented the same process in a JS app. It encodes the key and the message into bytes, then calculates an HMAC-SHA256 digest of the message using the key.

Does anyone see where I might be going wrong here? I've been debugging for a while and can't seem to find the issue.

Tyler Kanz
  • 23
  • 5

0 Answers0