I am trying to delete object from amazon s3 with REST API.
I create a curl command but I get 403 error: "SignatureDoesNotMatchThe request signature we calculated does not match the signature you provided".
I used in AWS documentation:
https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectDELETE.html
https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
This is my code:
$host = 's3.amazonaws.com';
$url = "https://" . $bucket . '.' . $host . '/' . $file;
$date_full = date('D, d M Y H:i:s \G\M\T');
$date = date('Ymd');
$longDate = gmdate('Ymd\THis\Z');
$shortDate = gmdate('Ymd');
$credential = $accessKeyId . '/' . $shortDate . '/' . $region . '/s3/aws4_request';
$hashed_payload = strtolower(hash("sha256", ""));
$headers = [
'X-Amz-Algorithm' => 'AWS4-HMAC-SHA256',
'X-Amz-Credential' => $credential,
'X-Amz-Date' => $longDate,
'X-Amz-Expires' => 86400,
'X-Amz-SignedHeaders' => 'host',
'x-amz-content-sha256' => $hashed_payload,
'Host' => $bucket . '.' . $host,
];
ksort($headers);
$signed_headers_string = strtolower(implode(';', array_keys($headers)));
// Build canonical request
$canonical_request = "DELETE\n"; // HTTPRequestMethod
$canonical_request .= '/' . urlencode("http://s3.amazonaws.com/" . $bucket . "/" . $file) . "\n"; // CanonicalURI
$canonical_request .= "\n"; // CanonicalQueryString
foreach ($headers as $header => $value) {
$canonical_request .= strtolower($header) . ':' . trim($value) . "\n"; //CanonicalHeaders
}
$canonical_request .= $signed_headers_string . "\n"; // SignedHeaders
$canonical_request .= $hashed_payload; // HashedPayload
// Build string to sign
$string_to_sign = "AWS4-HMAC-SHA256\n";
$string_to_sign .= $date_full . "\n";
$string_to_sign .= $date . '/' . $region. "/s3/aws4_request\n";
$string_to_sign .= hash('sha256', $canonical_request);
// Calculate signature
$signature_date = hash_hmac('sha256', $date, 'AWS4' . $secretKey, true);
$signature_region = hash_hmac('sha256', $region, $signature_date, true);
$signature_service = hash_hmac('sha256', 's3', $signature_region, true);
$signature_request = hash_hmac('sha256', 'aws4_request', $signature_service, true);
// Build Signature
$signature = hash_hmac('sha256', $string_to_sign, $signature_request);
// Calculate final Authorization header
$headers['Authorization'] = 'AWS4-HMAC-SHA256 Credential=' . $credential . ', ';
$headers['Authorization'] .= 'SignedHeaders=' . $signed_headers_string . ', ';
$headers['Authorization'] .= 'Signature=' . $signature;
// Convert headers to key:value strings
$curl_headers = array();
foreach ($headers as $header => $value) {
$curl_headers[] = "{$header}:{$value}";
}
// Init curl
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_VERBOSE, 1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $curl_headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
$result = curl_exec($curl);
Someone can help me please to understand what is the problem?
You are sorting keys that have upper and lower case letters. Use all lower case letters in the keys before calling ksort().
You are also including headers that don't need to be included. The documentation you referred to only asks for x-amz-date, x-amz-content-sha256, and host. Use only those headers.
$headers = [
'x-amz-date' => $longDate,
'x-amz-content-sha256' => $hashed_payload,
'host' => $bucket . '.' . $host,
];
ksort($headers);
Your canonical request should have an extra line break after adding the headers:
foreach ($headers as $header => $value) {
$canonical_request .= strtolower($header) . ':' . trim($value) . "\n"; //CanonicalHeaders
}
$canonical_request .= "\n";
I'm not sure if this change is necessary, but in $string_to_sign, I use the date format provided by $longDate, not $date_full:
// Build string to sign
$string_to_sign = "AWS4-HMAC-SHA256\n";
$string_to_sign .= $longDate . "\n";
$string_to_sign .= $date . '/' . $region. "/s3/aws4_request\n";
$string_to_sign .= hash('sha256', $canonical_request);
You are setting CURLOPT_CUSTOMREQUEST twice, first to PUT and then to DELETE. The last setting is likely the one being used, but I would remove curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT'); for the sake of clarity.
Related
I want to use an endpoint that uses an url parameter with spaces in it.
I have a function that generates an signature.
function generateSignature($request, $timestamp, $nonce)
{
Global $consumerKey, $accessToken, $consumerSecret, $tokenSecret, $realm, $signatureMethod, $version, $limit;
$baseparams = "";
$baseurl = strtok($request['url'], "?");
$base = strtoupper($request['method']) . "&" . urlencode($baseurl) . "&";
if(strpos($request['url'], '?') !== false){
$url_components = parse_url($request['url']);
parse_str($url_components['query'], $params);
$paramnr = 0;
foreach($params as $param => $value){
$paramnr++;
if($paramnr > 1){
$baseparams .= "&".$param."=" . urlencode($value);
}
else{
$baseparams .= $param."=" . urlencode($value);
}
}
$trailingand = "&";
}
else{
$trailingand = "";
}
echo $baseparams .= $trailingand
. "oauth_consumer_key=" . urlencode($consumerKey)
. "&oauth_nonce=" . urlencode($nonce)
. "&oauth_signature_method=" . urlencode($signatureMethod)
. "&oauth_timestamp=" . urlencode($timestamp)
. "&oauth_token=" . urlencode($accessToken)
. "&oauth_version=" . urlencode($version);
$base = $base.urlencode($baseparams);
$key = urlencode($consumerSecret) . '&' . urlencode($tokenSecret);
$signature = hash_hmac('sha256', $base, $key, true);
$signature = urlencode(base64_encode($signature));
return $signature;
}
My execute function:
function execute($options = array()){
Global $consumerKey, $accessToken, $consumerSecret, $tokenSecret, $realm, $signatureMethod, $version;
$curl = curl_init();
echo $options['url'];
curl_setopt_array($curl, array(
CURLOPT_URL => $options['url'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1
));
$timestamp = round(microtime(true));
$nonce = getNonce(11);
$signature = generateSignature($options, $timestamp, $nonce);
$authHeader = 'Authorization: OAuth realm="'.$realm.'",oauth_consumer_key="'.$consumerKey.'",oauth_token="'.$accessToken.'",oauth_signature_method="'.$signatureMethod.'",oauth_timestamp="'.$timestamp.'",oauth_nonce="'.$nonce.'",oauth_version="'.$version.'",oauth_signature="'.$signature.'"';
curl_setopt( $curl, CURLOPT_CUSTOMREQUEST, strtoupper($options['method']) );
curl_setopt($curl, CURLOPT_HTTPHEADER, array(
$authHeader
));
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
$response = curl_exec($curl);
$array = json_decode($response, true);
echo "<br/>".$responsecode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if(curl_errno($curl)){
return 'Curl error: ' . curl_error($curl);
}
else{
return $array;
}
curl_close($curl);
}
This works fine when I have an url like this:
https://website.com/api/customer?limit=10 - WORKS
But the API also supports this:
https://website.com/api/customer?q=partner+EQUALS+10 - DOESNT WORK
When I put this URL into my function, it doesn't work and I get an 400 error instead.
My guess is that the signature is incorrect due to the spaces inside the url parameter.
How can I make this work with an url parameter that has spaces in it?
I already tried to use urlencode but that didnt work as well.
If it helps you out: I'm using the NETSUITE REST API for this.
Can someone help me plz...
When you make a req use encoded url insted of spaces.
https://website.com/api/customer?q=partner+EQUALS+10
this type will works for you
A few months ago I implemented a RESTlet solution that receives a POST request from a PHP script. I am now extending the solution to send a GET request to a RESTlet with a different ID. The issue is that the authentication is only successful when the request is sent to a URL without the query string. We need to pass the query string to the RESTlet in order for it to execute.
Appending the query string to the URL throws a 403 INVALID_LOGIN error, and calling the RESTlet without it throws an exception for a missing paramter (as expected).
private function setAuthentication() {
$oauthNonce = md5(mt_rand());
$oauthTimestamp = time();
$baseString = $this->requestType."&".urlencode($this->url)."&".urlencode(
"deploy=".$this->deployID
."&oauth_consumer_key=".$this->consumerKey
."&oauth_nonce=".$oauthNonce
."&oauth_signature_method=".$this->oauthSigMethod
."&oauth_timestamp=".$oauthTimestamp
."&oauth_token=".$this->tokenID
."&oauth_version=".$this->oauthVersion
."&realm=".$this->account
."&script=".$this->scriptID
);
$sigString = urlencode($this->consumerSecret).'&'.urlencode($this->tokenSecret);
$signature = base64_encode(hash_hmac('sha1', $baseString, $sigString, true));
$authHeader = "OAuth "
. 'oauth_signature="' . rawurlencode($signature) . '", '
. 'oauth_version="' . rawurlencode($this->oauthVersion) . '", '
. 'oauth_nonce="' . rawurlencode($oauthNonce) . '", '
. 'oauth_signature_method="' . rawurlencode($this->oauthSigMethod) . '", '
. 'oauth_consumer_key="' . rawurlencode($this->consumerKey) . '", '
. 'oauth_token="' . rawurlencode($this->tokenID) . '", '
. 'oauth_timestamp="' . rawurlencode($oauthTimestamp) . '", '
. 'realm="' . rawurlencode($this->account) .'"';
return $authHeader;
}
public function callGetRestlet() {
$this->requestType = 'GET';
$authorizationHeader = $this->setAuthentication();
$urlQueryAppend = http_build_query(array('id' => $this->queryString));
$ch = curl_init();
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->requestType);
curl_setopt($ch, CURLOPT_URL, $this->url. '?&script='. $this->scriptID. '&deploy='. $this->deployID.'&realm=' . $this->account);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: '.$authorizationHeader,
'Content-Type: application/json',
]);
$response = array();
$response['request'] = json_decode($this->queryString);
$response['response']['body'] = json_decode(curl_exec($ch));
$response['response']['code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $response;
}
I know the credentials are okay as the RESTlet's event log shows the exception when the parameter isn't provided. The above throws the exception.
When the CURLOPT_URL is modified to include the querystring it throws the 403:
curl_setopt($ch, CURLOPT_URL, $this->url. '?&script='. $this->scriptID. '&deploy='. $this->deployID.'&realm=' . $this->account.'&'.$urlQueryAppend);
This code was slightly modified from an answer to a different question that asked about a POST request.
Is the error being thrown as a result of setting the request headers?
Adding the id parameter to the base string after the deploy ID resolved the issue. I was adding it to the end, but in this case it looks like the order of the parameters matters.
private function setAuthentication() {
$oauthNonce = md5(mt_rand());
$oauthTimestamp = time();
$baseString = $this->requestType."&".urlencode($this->url)."&".urlencode(
"deploy=".$this->deployID
."&id=".$this->queryString
."&oauth_consumer_key=".$this->consumerKey
."&oauth_nonce=".$oauthNonce
."&oauth_signature_method=".$this->oauthSigMethod
."&oauth_timestamp=".$oauthTimestamp
."&oauth_token=".$this->tokenID
."&oauth_version=".$this->oauthVersion
."&script=".$this->scriptID
);
$sigString = urlencode($this->consumerSecret).'&'.urlencode($this->tokenSecret);
$signature = base64_encode(hash_hmac('sha256', $baseString, $sigString, true));
$authHeader = "OAuth "
. 'oauth_signature="' . rawurlencode($signature) . '", '
. 'oauth_version="' . rawurlencode($this->oauthVersion) . '", '
. 'oauth_nonce="' . rawurlencode($oauthNonce) . '", '
. 'oauth_signature_method="' . rawurlencode($this->oauthSigMethod) . '", ' /** must be HMAC-SHA256 as SHA1 is deprecated **/
. 'oauth_consumer_key="' . rawurlencode($this->consumerKey) . '", '
. 'oauth_token="' . rawurlencode($this->tokenID) . '", '
. 'oauth_timestamp="' . rawurlencode($oauthTimestamp) . '", '
. 'realm="' . rawurlencode($this->account) .'"';
return $authHeader;
}
public function callGetRestlet() {
$this->requestType = 'GET';
$parameters = http_build_query(
array(
'script' => $this->scriptID,
'deploy' => $this->deployID,
'id' => $this->queryString,
)
);
$authorizationHeader = $this->setAuthentication();
$ch = curl_init();
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->requestType);
curl_setopt($ch, CURLOPT_URL, $this->url. '?'.$parameters);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 6);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: '.$authorizationHeader,
'Content-Type: application/json',
]);
$response = array();
$response['request'] = $this->url. '?'.$parameters;
$response['response']['body'] = json_decode(curl_exec($ch));
$response['response']['code'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $response;
}
I use Php for twitter autorization (OAuth). Can not get user email in response.
define('ACCOUNT_EMAIL','https://api.twitter.com/1.1/account/verify_credentials.json');
$oauth_nonce = md5(uniqid(rand(), true));
$oauth_timestamp = time();
$oauth_token = $response['oauth_token'];
$oauth_token_secret = $response['oauth_token_secret'];
$screen_name = $response['screen_name'];
//create oauth signature
$params = array(
'oauth_consumer_key=' . CONSUMER_KEY . URL_SEPARATOR,
'oauth_nonce=' . $oauth_nonce . URL_SEPARATOR,
'oauth_signature_method=HMAC-SHA1' . URL_SEPARATOR,
'oauth_timestamp=' . $oauth_timestamp . URL_SEPARATOR,
'oauth_token=' . $oauth_token . URL_SEPARATOR,
'oauth_version=1.0' . URL_SEPARATOR,
'screen_name=' . $screen_name,
//'include_email=true'
);
$oauth_base_text = 'GET' . URL_SEPARATOR . urlencode(ACCOUNT_EMAIL) . URL_SEPARATOR . implode('', array_map('urlencode', $params));
$key = CONSUMER_SECRET . '&' . $oauth_token_secret;
$signature = base64_encode(hash_hmac("sha1", $oauth_base_text, $key, true));
// get userinfo
$params = array(
'oauth_consumer_key=' . CONSUMER_KEY,
'oauth_nonce=' . $oauth_nonce,
'oauth_signature=' . urlencode($signature),
'oauth_signature_method=HMAC-SHA1',
'oauth_timestamp=' . $oauth_timestamp,
'oauth_token=' . urlencode($oauth_token),
'oauth_version=1.0',
'screen_name=' . $screen_name,
//'include_email=true'
);
$url = ACCOUNT_EMAIL . '?' . implode(URL_SEPARATOR, $params);
$response = file_get_contents($url);
print_r($response);
When **include_email=true is open - i get nothing.
When include_email=true is block - i get all userinfo (without email).**
In my admin profile I set Request email addresses from users and send info for special permission in twitter support.
Regenerate keys too.
What is problem in it&
Add cURL
$curl = curl_init();
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_HEADER, TRUE);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLINFO_HEADER_OUT, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($curl);
curl_close($curl);
print_r($result);
1 way
url = https://api.twitter.com/1.1/account/verify_credentials.json?oauth_consumer_key=XXXXXXXX&oauth_nonce=XXXXXX&oauth_signature=XXXXX&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1509963372&oauth_token=XXXXXX&oauth_version=1.0&screen_name=ssds
result = HTTP/1.1 200 OK + all field without email
2 way
url = https://api.twitter.com/1.1/account/verify_credentials.json?oauth_consumer_key=XXXXXXXX&oauth_nonce=XXXXXX&oauth_signature=XXXXX&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1509963372&oauth_token=XXXXXX&oauth_version=1.0&screen_name=ssds&include_email=true
result = HTTP/1.1 401 Authorization Required + {"errors":[{"code":32,"message":"Could not authenticate you."}]}
2 way with email not work.
I solved this problem and get email!
This is view of last url
define('ACCOUNT_EMAIL','https://api.twitter.com/1.1/account/verify_credentials.json');
curl_setopt($curl, CURLOPT_URL, ACCOUNT_EMAIL . '?include_email=true&screen_name='.$screen_name);
We have a Lambda function that is called from the AWS API Gateway. I have been trying to call it from my PHP code. I am using this document to try to get the 'Authorization' header right:
http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationDebugging
But I must be missing something, because I keep getting the error:
{"message":"Missing Authentication Token"}
Here's what I have so far:
current_date = date('D, d M Y H:i:s +u');
$tmp1 = explode('/',AWS_URL);
write_log("URL exploded" . json_encode($tmp1));
$tmp2 = explode('.',$tmp1[2]);
write_log("IP exploded: " . json_encode($tmp2));
$url_parts = $tmp2[0] . '/' . $tmp1[3] . '/' . $tmp1[4];
$to_cannonicalize = 'POST\nContent-MD5\nTontent-Type\n' . $current_date . '\n' . $url_parts;
write_log("cannocalize this string: " . $to_cannonicalize);
$signature = base64_encode(hash_hmac('sha256', $to_cannonicalize, AWS_LABMDA_SECRET));
$formatted_date = date('Ymd');
$zdate = date('Ymd') . 'T' . date('His') . 'Z';
$authorization = 'AWS ' . AWS_LABMDA_KEY . ':' . $signature;
// set up the CURL call
$header_array = array (
'Content-type' => 'application/json',
'Authorization' => $authorization,
'X-Amz-Date' => $zdate
);
write_log(json_encode($header_array));
$post_data = array (
'postalcode' => $zip
);
$curl = curl_init( AWS_TAXRATE_URL );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl, CURLOPT_POST, 1 );
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt( $curl, CURLOPT_POSTFIELDS, $post_data );
// make call and send results
$curl_response = curl_exec( $curl );
I also tried copying what was being used in Postman as a header (where I'm getting a different type of error):
$authorization = 'AWS4-HMAC-SHA256 Credential=' . AWS_LABMDA_KEY . '/' . $formatted_date . '/'
. AWS_SQS_REGION . '/execute-api/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date, '
. 'Signature=' . $signature;
That didn't seem to work either.
The following PHP code working perfect in development environment but on production environment returns error :
{"uuid":"xxxxx", "serverErrorCode":"AUTHENTICATION_FAILED", "reason":"Authentication failed"}
any idea ?
function cloudKitRequest($requestType, $jsonbody)
{
$KEY_ID = '97659598759875987508750875087087608785987585985';
$CONTAINER = 'xyz.zz.zzyyxx.xyz';
$PRIVATE_PEM_LOCATION = 'eckey.pem';
//$Environment = 'development';
$Environment = 'production';
$dbtype = 'public';
$result = NULL;
$url = 'https://api.apple-cloudkit.com/database/1/' . $CONTAINER . '/'. $Environment . '/' . $dbtype . '/records/'.$requestType;
// Set cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER, false);
// Create signature
date_default_timezone_set('UTC');
$explode_date = explode('+', date("c", time()));
$time = $explode_date[0] . 'Z';
$signature = $time . ":" . base64_encode(hash("sha256", $jsonbody, true)) . ":" . explode('cloudkit.com', $url)[1];
// Get private key
$pkeyid = openssl_pkey_get_private("file://" . $PRIVATE_PEM_LOCATION);
// Sign signature with private key
if(openssl_sign($signature, $signed_signature, $pkeyid, "sha256WithRSAEncryption")) {
openssl_free_key($pkeyid);
// Set headers
curl_setopt($ch, CURLOPT_HTTPHEADER,
[
"Content-Type: text/plain",
"X-Apple-CloudKit-Request-KeyID: " . $KEY_ID,
"X-Apple-CloudKit-Request-ISO8601Date: " . $time,
"X-Apple-CloudKit-Request-SignatureV1: " . base64_encode($signed_signature),
]
);
// Set body
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonbody);
// Send the request & save response to $resp
$resp = curl_exec($ch);
if($resp === false) {
die('Error: "' . curl_error($ch) . '" - Code: ' . curl_errno($ch));
} else {
//echo $resp;
// $result = json_decode($resp, true);
$result = $resp;
}
curl_close($ch);
} else {
while ($msg = openssl_error_string()) {
echo $msg . "<br />\n";
}
}
return $result;
}
any idea