Reputation: 753
Im writing a AWS API request to list users on IAM AWS service. And I'm receiving error message.
<ErrorResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<Error>
<Type>Sender</Type>
<Code>SignatureDoesNotMatch</Code>
<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.
The Canonical String for this request should have been
'GET
/
Action=ListUsers&Version=2010-05-08&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIMPILWMPQSH57DNA%2F20160621%2Fus-east-1%2Fiam%2Faws4_request&X-Amz-Date=20160621T142939Z&X-Amz-SignedHeaders=host
host:iam.amazonaws.com
host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20160621T142939Z
20160621/us-east-1/iam/aws4_request
c47728e278701ccaada8df76488c18449ada2f1b8aab6275a4bc0ada94af3ce2'
</Message>
</Error>
<RequestId>9672bcf2-37bc-11e6-8b2d-6151d0618c53</RequestId>
</ErrorResponse>
As you can see from my code bellow my canonical string is exactly the same like they wrote in error response but for some reason when im calculating hash my hex value is different that they write. For example in this error response they wrote that hex value should be
`c47728e278701ccaada8df76488c18449ada2f1b8aab6275a4bc0ada94af3ce2`
and when I use functions
$hashedcanon = hash_hmac("sha256", $canonicalrequest, True);
in my code im getting
`57fce72007b43c2621712b85e90fd38f0a1f2c7a3e84799fb9f477ed8546f86e`
Here is my code.
<?php
$AWSAccessKeyId = "<myaccesskey>";
$SecretAccessKey = "<mysecretkey>";
$timestamp = date('Ymd',time()).'T'.date('His',time()).'Z';
$date = date('Ymd',time());
$url = 'https://iam.amazonaws.com';
$method = 'GET';
$postfields['Action'] = 'ListUsers';
$postfields['Version'] = '2010-05-08';
$postfields["X-Amz-Algorithm"] = 'AWS4-HMAC-SHA256';
$postfields['X-Amz-Credential'] = $AWSAccessKeyId.'/'.$date.'/us-east-1/iam/aws4_request';
$postfields['X-Amz-Date'] = $timestamp;
$postfields['X-Amz-SignedHeaders'] = 'host';
$canonicalized_query = array();
foreach ($postfields as $param => $value) {
$param = str_replace("%7E", "~", rawurlencode($param));
$value = str_replace("%7E", "~", rawurlencode($value));
$canonicalized_query[] = $param . "=" . $value;
}
$canonicalized_query = implode("&", $canonicalized_query);
$canonicalrequest = $method."\n".
"/\n".
$canonicalized_query."\n".
"host:iam.amazonaws.com\n".
"\n".
"host\n".
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
$hashedcanon = hash_hmac("sha256", $canonicalrequest, True);
$string_to_sign = $postfields["X-Amz-Algorithm"]."\n".$timestamp."\n".$date."/us-east-1/iam/aws4_request\n".$hashedcanon;
$signingkey = hash_hmac("sha256",hash_hmac("sha256",hash_hmac("sha256",hash_hmac("sha256","AWS4".$SecretAccessKey,$date),"us-east-1"),"iam"),"aws4_request");
$signature = hash_hmac("sha256", $string_to_sign, $signingkey, True);
$postfields["X-Amz-Signature"] = $signature;
$canonicalized_query = array();
foreach ($postfields as $param => $value) {
$param = str_replace("%7E", "~", rawurlencode($param));
$value = str_replace("%7E", "~", rawurlencode($value));
$canonicalized_query[] = $param . "=" . $value;
}
$canonicalized_query = implode("&", $canonicalized_query);
$fullurl = $url.'/?'.$canonicalized_query;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $fullurl);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLINFO_HEADER_OUT, true); // enable tracking
$result = curl_exec($ch);
$headerSent = curl_getinfo($ch, CURLINFO_HEADER_OUT );
?>`
So in general I'm assuming I'm calculating wrong hex value of string to sign since my and theirs hex value of canonical string are not the same.
Also is interesting when I copy/paste my canonical string to http://hash.online-convert.com/sha256-generator
I'm getting third hex value (not even my or theirs).
If anyone needs more info I'm willing to provide it or if anyone has working code for any API AWS if it can share it so I throw an eye and compare and hope I can find error. Thanks
Upvotes: 1
Views: 673
Reputation: 753
Hi guys I will post now fixed and working code just if someone else needs it.
<?php
$AWSAccessKeyId = "<my access key>";
$SecretAccessKey = "<my secret key>";
$timestamp = date('Ymd',time()).'T'.date('His',time()).'Z';
$date = date('Ymd',time());
$url = 'https://iam.amazonaws.com';
$method = 'GET';
$postfields['Action'] = 'ListUsers';
$postfields['Version'] = '2010-05-08';
$postfields["X-Amz-Algorithm"] = 'AWS4-HMAC-SHA256';
$postfields['X-Amz-Credential'] = $AWSAccessKeyId.'/'.$date.'/us-east-1/iam/aws4_request';
$postfields['X-Amz-Date'] = $timestamp;
$postfields['X-Amz-SignedHeaders'] = 'host';
$canonicalized_query = array();
foreach ($postfields as $param => $value) {
$param = str_replace("%7E", "~", rawurlencode($param));
$value = str_replace("%7E", "~", rawurlencode($value));
$canonicalized_query[] = $param . "=" . $value;
}
$canonicalized_query = implode("&", $canonicalized_query);
$canonicalrequest = $method."\n".
"/\n".
$canonicalized_query."\n".
"host:iam.amazonaws.com\n".
"\n".
"host\n".
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
$hasedcanon = hash("sha256",$canonicalrequest, false);
$credentialScope = $date."/us-east-1/iam/aws4_request";
$string_to_sign = "AWS4-HMAC-SHA256\n{$timestamp}\n{$credentialScope}\n{$hasedcanon}";
$dateKey = hash_hmac('sha256',$date,"AWS4{$SecretAccessKey}",true);
$regionKey = hash_hmac('sha256', "us-east-1", $dateKey, true);
$serviceKey = hash_hmac('sha256', "iam", $regionKey, true);
$key = hash_hmac('sha256','aws4_request',$serviceKey,true);
$signature = hash_hmac('sha256', $string_to_sign, $key);
$postfields["X-Amz-Signature"] = $signature;
$canonicalized_query = array();
foreach ($postfields as $param => $value) {
$param = str_replace("%7E", "~", rawurlencode($param));
$value = str_replace("%7E", "~", rawurlencode($value));
$canonicalized_query[] = $param . "=" . $value;
}
$canonicalized_query = implode("&", $canonicalized_query);
$fullurl = $url.'/?'.$canonicalized_query;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $fullurl);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLINFO_HEADER_OUT, true); // enable tracking
$xml = curl_exec($ch);
$headerSent = curl_getinfo($ch, CURLINFO_HEADER_OUT );
$doc = simplexml_load_string($xml);
echo '<pre>';
print_r($doc);
echo '</pre>';
?>
Upvotes: 1
Reputation: 178956
$hashedcanon = hash_hmac("sha256", $canonicalrequest, True);
Well, three issues...
hash_hmac()
takes the key as the third argument, not a boolean, and
you aren't supposed to be calculating an HMAC digest here, so hash_hmac()
isn't what you want, and
you want it in hex, not binary, so don't pass True
.
You're looking for just hash()
.
$hashedcanon = hash("sha256", $canonicalrequest);
Note, I'm not saying hash_hmac()
is not needed elsewhere -- just not on this line.
Upvotes: 2