1. Anuncie Aqui ! Entre em contato fdantas@4each.com.br

[Flutter] AWS SignatureDoesNotMatch Error with Canonical Request in AWS Bedrock API using Dio...

Discussão em 'Mobile' iniciado por Stack, Outubro 1, 2024 às 08:43.

  1. Stack

    Stack Membro Participativo

    I am trying to send a POST request to the AWS Bedrock API using the Dio package in Dart with custom request signing using AWS Signature Version 4. However, I keep encountering the following error:

    {
    "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."
    }


    I have followed the AWS documentation for Signature Version 4, and I calculate the signature by generating a canonical request, string to sign, and signing key. Here’s the relevant part of my code:

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

    class AWSInterceptor extends Interceptor {
    final String accessKey;
    final String secretKey;
    final String region;
    final String service;
    final String sessionToken;

    AWSInterceptor({
    required this.accessKey,
    required this.secretKey,
    required this.region,
    required this.service,
    required this.sessionToken,
    });

    @override
    void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final uri = options.uri;
    final method = options.method;
    final headers = options.headers;
    final payload = options.data ?? '';

    // Handling payload, sending '{}' when payload is empty
    String payloadString = payload is Map ? json.encode(payload) : '{}';

    // Calculating SHA-256 hash of the payload
    final payloadHash = sha256.convert(utf8.encode(payloadString)).toString();
    headers['x-amz-content-sha256'] = payloadHash;
    headers['X-Amz-Security-Token'] = sessionToken;
    headers['Host'] = uri.host;
    headers['Accept'] = headers['Accept'] ?? 'application/json';
    headers['Content-Type'] = headers['Content-Type'] ?? 'application/json';

    // Formatting date for AWS
    final amzDate = DateFormat('yyyyMMdd\'T\'HHmmss\'Z\'').format(DateTime.now().toUtc());
    final dateStamp = DateFormat('yyyyMMdd').format(DateTime.now().toUtc());
    headers['x-amz-date'] = amzDate;

    // Building the canonical request
    final canonicalUri = uri.path.isEmpty ? '/' : uri.path;
    final canonicalQueryString = _buildCanonicalQueryString(uri.queryParameters);
    final canonicalHeaders = _buildCanonicalHeaders(headers);
    final signedHeaders = _buildSignedHeaders(headers);

    final canonicalRequest = [
    method,
    canonicalUri,
    canonicalQueryString,
    canonicalHeaders,
    signedHeaders,
    payloadHash,
    ].join('\n');

    // Creating the string to sign
    final credentialScope = '$dateStamp/$region/$service/aws4_request';
    final stringToSign = [
    'AWS4-HMAC-SHA256',
    amzDate,
    credentialScope,
    sha256.convert(utf8.encode(canonicalRequest)).toString(),
    ].join('\n');

    // Calculating the signing key
    final signingKey = _calculateSigningKey(secretKey, dateStamp, region, service);
    final signature = _hexEncode(hmacSha256(signingKey, utf8.encode(stringToSign)));

    // Adding the Authorization header
    final authorizationHeader = [
    'AWS4-HMAC-SHA256 Credential=$accessKey/$credentialScope',
    'SignedHeaders=$signedHeaders',
    'Signature=$signature',
    ].join(', ');

    headers['Authorization'] = authorizationHeader;

    // Debug outputs
    print('Canonical Request:\n$canonicalRequest');
    print('String to Sign:\n$stringToSign');
    print('Signature Key: ${_hexEncode(signingKey)}');
    print('Signature:\n$signature');
    print('Authorization Header:\n$authorizationHeader');

    return handler.next(options);
    }

    String _buildCanonicalQueryString(Map<String, dynamic> queryParams) {
    if (queryParams.isEmpty) return '';
    final sortedParams = queryParams.entries.toList()..sort((a, b) => a.key.compareTo(b.key));
    return sortedParams.map((e) => '${Uri.encodeQueryComponent(e.key)}=${Uri.encodeQueryComponent(e.value.toString())}').join('&');
    }

    String _buildCanonicalHeaders(Map<String, dynamic> headers) {
    final sortedHeaders = headers.entries
    .where((entry) => entry.value != null)
    .map((entry) => MapEntry(entry.key.toLowerCase(), entry.value.toString().trim()))
    .toList()
    ..sort((a, b) => a.key.compareTo(b.key));

    return '${sortedHeaders.map((entry) => '${entry.key}:${entry.value}').join('\n')}\n';
    }

    String _buildSignedHeaders(Map<String, dynamic> headers) {
    final sortedKeys = headers.keys
    .map((key) => key.toLowerCase())
    .where(
    (key) => [
    'host',
    'x-amz-content-sha256',
    'x-amz-date',
    'x-amz-security-token',
    'content-type',
    'accept',
    ].contains(key),
    )
    .toList()
    ..sort();

    return sortedKeys.join(';');
    }

    List<int> _calculateSigningKey(String secretKey, String dateStamp, String region, String service) {
    final kDate = hmacSha256(utf8.encode('AWS4$secretKey'), utf8.encode(dateStamp));
    final kRegion = hmacSha256(kDate, utf8.encode(region));
    final kService = hmacSha256(kRegion, utf8.encode(service));
    final kSigning = hmacSha256(kService, utf8.encode('aws4_request'));
    return kSigning;
    }

    List<int> hmacSha256(List<int> key, List<int> message) {
    final hmac = Hmac(sha256, key);
    final digest = hmac.convert(message);
    return digest.bytes;
    }

    String _hexEncode(List<int> bytes) {
    return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
    }
    }


    Debug Outputs:

    • Canonical Request, String to Sign, Signature all seem to be correctly printed as per the AWS documentation.
    • I have already confirmed the correct AWS credentials (Access Key, Secret Key, and Session Token).

    What I've Tried:

    1. Verifying that the x-amz-date is in UTC and formatted correctly.
    2. Ensuring that the payload is properly encoded as JSON.
    3. Canonicalizing the query string and headers according to AWS guidelines.

    Where might the discrepancy be coming from in the signature calculation? Is there something wrong with how I build the canonical request or sign the request? Any suggestions would be appreciated!

    Continue reading...

Compartilhe esta Página