0

I am trying to create an Authorized Signature with IAM to the Rekognition endpoint. It works fine in Postman using Postman's internal AWS Signature, but trying to generate the Authorization Header + Signature (in PHP) and enter it in manually is proving to be impossible.

enter image description here

I've taken a look at the following questions and answers in:

None of these solutions seem worked for me. What I'm trying to do is to have a PHP function that generate the Authorization Header, the Date header, ect., and to paste this into Postman to get it to return a result.

It doesn't matter what combination I try (omit payload, add payload, add param, omit params) I get:

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.

Yes I have checked the Access keys, and data is the same JSON in both PHP script and Postman -- it works with Postman's "AWS Signuature"

But below will not work?

enter image description here

The code I'm trying here:

function sign_request($url, $data) {
      
  $host                 = 'rekognition.eu-west-2.amazonaws.com';
  $access_key               = '<<Acess_Key>>';
  $secret_key           = '<<Secret_Key>>';
  $region               = '<<Region>>';
  $service              = 'rekognition';

  $current = new DateTime('UTC');
  $current_date_time = $current->format('Ymd\THis\Z');
  $current_date = $current->format('Ymd');

  $signed_headers = [
    'content-type' => 'application/x-amz-json-1.1',
    'host' => $host,
    'x-amz-date' => $current_date_time,
    'x-amz-target' => 'RekognitionService.DetectFaces'
    //'x-amz-security-token' => $token, // leave this one out if you have a IAM created fixed access key - secret pair and do not need the token.
  ];
  $signed_headers_string = implode(';', array_keys($signed_headers));

  $canonical = [
    'POST',
    parse_url($url, PHP_URL_PATH),
    parse_url($url, PHP_URL_QUERY), 
  ];
  
  foreach ($signed_headers as $header => $value) {
    $canonical[] = "$header:$value";
  }
  $canonical[] = ''; 
  $canonical[] = $signed_headers_string;
  $canonical[] = hash('sha256', http_build_query($data));
  $canonical = implode("\n", $canonical);

  $credential_scope = [$current_date, $region, $service, 'aws4_request'];
  $key = array_reduce($credential_scope, fn ($key, $credential) => hash_hmac('sha256', $credential, $key, TRUE), 'AWS4' . $secret_key);
  $credential_scope = implode('/', $credential_scope);

  $string_to_sign = implode("\n", [
    'AWS4-HMAC-SHA256',
    $current_date_time,
    $credential_scope,
    hash('sha256', $canonical),
  ]);
  $signature = hash_hmac('sha256', $string_to_sign, $key);

  unset($signed_headers['host']);
  $signed_headers['Authorization'] = "AWS4-HMAC-SHA256 Credential=$access_key/$credential_scope, SignedHeaders=$signed_headers_string, Signature=$signature";
  return $signed_headers;
}


$requestUrl = 'https://rekognition.eu-west-2.amazonaws.com/?Action=DetectFaces';
$body = [
    "Attributes"=> [
        "ALL"
    ],
    "Image"> [
        "Bytes"=> "<<BASE64_of_Face_Image>>"
    ]
];

print_r(sign_request($requestUrl, $body ));

Am I missing something?

Jason
  • 560
  • 3
  • 10
  • 25
  • Please add all relevant code to the question itself, instead of as a link off-site. If not, then the question won't be as useful for future visitors. You can read [How to create a Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) and [how to ask](https://stackoverflow.com/help/how-to-ask) for more info. – M. Eriksson Jun 08 '23 at 14:38
  • Sure, added the code above – Jason Jun 08 '23 at 16:10

1 Answers1

0

I got it to work by adding x-amz-content-sha256 to the $signed_headers array above.

The headers also have to be in alphabetical order as mentioned here: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html section StringToSign

$signed_headers = [
'content-type' => 'application/x-amz-json-1.1',
'host' => $host,
'x-amz-content-sha256' => hash('sha256', $data),
'x-amz-date' => $current_date_time,
'x-amz-target' => 'RekognitionService.DetectFaces',
//'x-amz-security-token' => $token, // leave this one out if you have a IAM created fixed access key - secret pair and do not need the token.
  ];

Just to be clear, the hash payload was needed in the StringtoSign and the CanonicalRequest

From Amazon doc above: The x-amz-content-sha256 header is required for all AWS Signature Version 4 requests. It provides a hash of the request payload. If there is no payload, you must provide the hash of an empty string.

host:examplebucket.s3.amazonaws.com
range:bytes=0-9
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20130524T000000Z

host;range;x-amz-content-sha256;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 

New code:

function sign_request($url, $data) {
      
  $host                 = 'rekognition.eu-west-2.amazonaws.com';
  $access_key               = '<<Acess_Key>>';
  $secret_key           = '<<Secret_Key>>';
  $region               = '<<Region>>';
  $service              = 'rekognition';

  $current = new DateTime('UTC');
  $current_date_time = $current->format('Ymd\THis\Z');
  $current_date = $current->format('Ymd');

  $signed_headers = [
    'content-type' => 'application/x-amz-json-1.1',
    'host' => $host,
    'x-amz-content-sha256' => hash('sha256', $data),
    'x-amz-date' => $current_date_time,
    'x-amz-target' => 'RekognitionService.DetectFaces',
    //'x-amz-security-token' => $token, // leave this one out if you have a IAM created fixed access key - secret pair and do not need the token.
  ];
  $signed_headers_string = implode(';', array_keys($signed_headers));

  $canonical = [
    'POST',
    parse_url($url, PHP_URL_PATH),
    parse_url($url, PHP_URL_QUERY), 
  ];
  
  
  
  foreach ($signed_headers as $header => $value) {
    $canonical[] = "$header:$value";
  }
  $canonical[] = ''; 
  $canonical[] = $signed_headers_string;
  $canonical[] = (hash('sha256', ($data)));
  //print_r($canonical);
  
  $canonical = implode("\n", $canonical);

  $credential_scope = [$current_date, $region, $service, 'aws4_request'];
  $key = array_reduce($credential_scope, fn ($key, $credential) => hash_hmac('sha256', $credential, $key, TRUE), 'AWS4' . $secret_key);
  $credential_scope = implode('/', $credential_scope);

  $string_to_sign = implode("\n", [
    'AWS4-HMAC-SHA256',
    $current_date_time,
    $credential_scope,
    hash('sha256', $canonical),
  ]);
  echo hash('sha256', $canonical)."\n\n";
  $signature = hash_hmac('sha256', $string_to_sign, $key);

  //unset($signed_headers['host']);
  $signed_headers['Authorization'] = "AWS4-HMAC-SHA256 Credential=$access_key/$credential_scope, SignedHeaders=$signed_headers_string, Signature=$signature";
  return $signed_headers;
}


$requestUrl = 'https://rekognition.eu-west-2.amazonaws.com/?Action=DetectFaces';
$data = json_encode([
    "Attributes" => [
        "ALL"
    ],
    "Image" => [
        "Bytes"=> "<<BASE64_FACE_IMG>>"
    ]
]);

print_r(sign_request($requestUrl, $data ));
Jason
  • 560
  • 3
  • 10
  • 25