Ive cracked oAuth and have my class file for it. I'm at the last stage of posting a tweet and all works except all the words are joined with a plus sign in the tweet.
Changing anything results in the signature been incorrect and twitter returns 401 error.
So how does one remove the pluses? Post function below:
function post($token, $tokenSecret, $status)
{
// Default params
$params = array(
"oauth_version" => "1.0",
"oauth_nonce" => time(),
"oauth_timestamp" => time(),
"oauth_consumer_key" => $this->key,
"oauth_signature_method" => "HMAC-SHA1",
"oauth_token" => $token,
"status" => $status
);
uksort($params, 'strcmp');
// convert params to string
foreach ($params as $k => $v) {$pairs[] = $this->_urlencode_rfc3986($k).'='.$this->_urlencode_rfc3986($v);}
$concatenatedParams = implode('&', $pairs);
// form base string (first key)
$baseString= "POST&".$this->_urlencode_rfc3986($this->request_statuses_url)."&".$this->_urlencode_rfc3986($concatenatedParams);
// form secret (second key)
$secret = $this->_urlencode_rfc3986($this->secret)."&".$this->_urlencode_rfc3986($tokenSecret);
// make signature
$sig = $this->_urlencode_rfc3986(base64_encode(hash_hmac('sha1', $baseString, $secret, TRUE)));
// BUILD URL
$url = $this->request_statuses_url; // twitter update url
$paramString = $concatenatedParams."&oauth_signature=".$sig;
// Send to cURL
$result = $this->_http($url, $paramString);
if($result['httpCode'] == '200'){
// Return array
return $result;
}
else{
// Error
show_error($result['httpCode'], $result['httpCode']);
return FALSE;
}
}
Is $status your tweet? Take a look at the POST request before you post it, my guess is _urlencode_rfc3986() converts it so that you get "$status=This+is+my+tweet" when you want "$status=This is my tweet"
Twitter is not supporting "+" as escape for spaces, which as far as I know is a violation of the standard.
You have to replace the the + with %20.
Related
I am trying to get the jwt token, but i am getting errors everytime with everything that i try. Below are th things that i have tried. I do get the jwt-token without the package but when i use the jwt.io to check the signature verification,it fails every time. With the package i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
Without php-Jwt
$header = [
'alg' => "RS384",
'typ' => "JWT"
];
$payload = [
'iss' => 'my-client-id',
'sub' => 'my-client-id',
'aud' => "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
'jti' => (string)strtotime(gmdate("Y-m-d H:i:s")),
'exp' => strtotime(gmdate("Y-m-d H:i:s")) + 270,
];
$privateKey = "my private Key"
$headers_encoded = $this->base64url_encode(json_encode($header));
$payload_encoded = $this->base64url_encode(json_encode($payload));
$signature = hash_hmac('sha384', $headers_encoded.'.'.$payload_encoded, $privateKey, true);
// Encode the signature as a base64url string
$signature_encoded = $this->base64url_encode($signature);
$jwt = $headers_encoded.'.'.$payload_encoded.'.'.$signature_encoded;
- With php-jwt
installed the package
composer require firebase/php-jwt --ignore-platform-req=ext-mongodb
Used the required files in my controller
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
Tried Encoding:
$check = JWT::encode(
$header,
$payload,
$privateKey,
'RS384'
);
i get different errors like the private key cannot be coereced and sometimes invalid algorithm. Please correct me where i am messing it up.
In simple words, this is what I am doing or trying to do. A generic form of my code:
<?php
// Load the private key from a file
$privateKey = file_get_contents('private.key');
// Set the header and payload for the JWT
$header = [
'alg' => 'RS384',
'typ' => 'JWT'
];
$payload = [
'sub' => '1234567890',
'name' => 'John Doe',
'iat' => 1516239022
];
// Encode the header and payload as JSON strings
$headerEncoded = base64_encode(json_encode($header));
$payloadEncoded = base64_encode(json_encode($payload));
// Concatenate the header, payload, and secret to create the signature
$signature = hash_hmac('sha384', "$headerEncoded.$payloadEncoded", $privateKey, true);
// Encode the signature as a base64 string
$signatureEncoded = base64_encode($signature);
// Concatenate the header, payload, and signature to create the JWT
$jwt = "$headerEncoded.$payloadEncoded.$signatureEncoded";
echo $jwt;
I do get the jwt-signature but on https://jwt.io/ it shows unverified.
Fixed
Use this instead of hash_mac():
openssl_sign($data, $signature, $privateKey, OPENSSL_ALGO_SHA384);
Make sure if the key is stored in PHP variable, format the key like this:
$privateKey = "------BEGIN PRIVATE KEY------\n".
"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj\n".
"MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu\n".
.
.
.
"TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc\n".
"dn/RsYEONbwQSjIfMPkvxF+8HQ==\n".
"------END PRIVATE KEY------";
enclose every line in Double quotes "" and add \n at end of every line.
I am having some issues in creating a Discord bot. I want it to be able to respond to slash commands, but to do so I need to verify an endpoint.
I am using PHP 7.4, and can't use any external libraries (hosting on a server that does not allow them).
I have found documents for PHP, but they do require libraries to work.
I tried taking the documents from Node.JS and "converting" them to PHP.
Here's my code:
<?php
$public_key = "shh-it's-a-seekrit";
$headers = getallheaders();
$signature = $headers["X-Signature-Ed25519"];
$timestamp = $headers["X-Signature-Timestamp"];
$raw_body = file_get_contents('php://input');
/* To compute the signature, we need the following
* 1. Message ($timestamp + $body)
* 2. $signature
* 3. $public_key
* The algorythm is SHA-512
*/
$message = $timestamp . $raw_body;
$hash_signature = hash_hmac('sha512', $message, $public_key);
if (!hash_equals($signature, $hash_signature)) {
header("HTTP/1.1 401 Unauthorized", true, 401);
die("Request is not properly authorized!");
}
$return_array = [
'type' => 1,
];
echo json_encode($return_array);
?>
When I put the address the file is uploaded to and try to save the changes, Discord says the following:
Validation errors:
interactions_endpoint_url: The specified interactions endpoint URL could not be verified.
This is a method that works for me on PHP 8.1.
Pass in the headers and raw JSON body, and it returns an array with the response code and payload to send back through whatever you are using to handle the response. Note the response must be JSON encoded.
$discord_public is the public key from the Discord app.
public function authorize(array $headers, string $body, string $discord_public): array
{
$res = [
'code' => 200,
'payload' => []
];
if (!isset($headers['x-signature-ed25519']) || !isset($headers['x-signature-timestamp'])) {
$res['code'] = 401;
return $res;
}
$signature = $headers['x-signature-ed25519'];
$timestamp = $headers['x-signature-timestamp'];
if (!trim($signature, '0..9A..Fa..f') == '') {
$res['code'] = 401;
return $res;
}
$message = $timestamp . $body;
$binary_signature = sodium_hex2bin($signature);
$binary_key = sodium_hex2bin($discord_public);
if (!sodium_crypto_sign_verify_detached($binary_signature, $message, $binary_key)) {
$res['code'] = 401;
return $res;
}
$payload = json_decode($body, true);
switch ($payload['type']) {
case 1:
$res['payload']['type'] = 1;
break;
case 2:
$res['payload']['type'] = 2;
break;
default:
$res['code'] = 400;
return $res;
}
return $res;
}
For completeness, adding the missing code to #Coder1 answer:
<?php
$payload = file_get_contents('php://input');
$result = endpointVerify($_SERVER, $payload, 'discord app public key');
http_response_code($result['code']);
echo json_encode($result['payload']);
function endpointVerify(array $headers, string $payload, string $publicKey): array
{
if (
!isset($headers['HTTP_X_SIGNATURE_ED25519'])
|| !isset($headers['HTTP_X_SIGNATURE_TIMESTAMP'])
)
return ['code' => 401, 'payload' => null];
$signature = $headers['HTTP_X_SIGNATURE_ED25519'];
$timestamp = $headers['HTTP_X_SIGNATURE_TIMESTAMP'];
if (!trim($signature, '0..9A..Fa..f') == '')
return ['code' => 401, 'payload' => null];
$message = $timestamp . $payload;
$binarySignature = sodium_hex2bin($signature);
$binaryKey = sodium_hex2bin($publicKey);
if (!sodium_crypto_sign_verify_detached($binarySignature, $message, $binaryKey))
return ['code' => 401, 'payload' => null];
$payload = json_decode($payload, true);
switch ($payload['type']) {
case 1:
return ['code' => 200, 'payload' => ['type' => 1]];
case 2:
return ['code' => 200, 'payload' => ['type' => 2]];
default:
return ['code' => 400, 'payload' => null];
}
}
According to Discord documentation, interactions endpoint must do two things:
Your endpoint must be prepared to ACK a PING message
Your endpoint must be set up to properly handle signature headers--more on that in Security and Authorization
The first part is very simple:
So, to properly ACK the payload, return a 200 response with a payload of type: 1:
Thus, you need to return JSON object and not an array (maybe that's one of the problems with your code).
Additionally, endpoint must be able to respond to invalid requests as:
We will also do automated, routine security checks against your endpoint, including purposefully sending you invalid signatures. If you fail the validation, we will remove your interactions URL in the future and alert you via email and System DM.
One thing I noticed is that Discord sends their headers lowercased! Another problem is that you use:
$raw_body = file_get_contents('php://input'); that doesn't look right.
Finally, if you know node.JS take a look at my working example: https://github.com/iaforek/discord-interactions
following is the code which i am using with php to get xml response with amazon api service but it is giving me error stated as below of code.
<?php
// Your AWS Access Key ID, as taken from the AWS Your Account page
$aws_access_key_id = "A*********A";
// Your AWS Secret Key corresponding to the above ID, as taken from the AWS Your Account page
$aws_secret_key = "ue*******s+";
// The region you are interested in
$endpoint = "webservices.amazon.in";
$uri = "/onca/xml";
$params = array(
"Service" => "AWSECommerceService",
"Operation" => "ItemSearch",
"AWSAccessKeyId" => "AKIAJYSYWXDOF5CWIK5A",
"AssociateTag" => "unity0f-21",
"SearchIndex" => "All",
"Keywords" => "iphone",
"ResponseGroup" => "Images,ItemAttributes,Offers"
);
// Set current timestamp if not set
if (!isset($params["Timestamp"])) {
$params["Timestamp"] = gmdate('Y-m-d\TH:i:s\Z');
}
// Sort the parameters by key
ksort($params);
$pairs = array();
foreach ($params as $key => $value) {
array_push($pairs, rawurlencode($key)."=".rawurlencode($value));
}
// Generate the canonical query
$canonical_query_string = join("&", $pairs);
// Generate the string to be signed
$string_to_sign = "GET\n".$endpoint."\n".$uri."\n".$canonical_query_string;
// Generate the signature required by the Product Advertising API
$signature = base64_encode(hash_hmac("sha256", $string_to_sign, $aws_secret_key, true));
// Generate the signed URL
$request_url = 'http://'.$endpoint.$uri.'?'.$canonical_query_string.'&Signature='.rawurlencode($signature);
echo "Signed URL: \"".$request_url."\"";
?>
it is giving error as unsupported version i tried everything but still error is occurring please help
response of the request for the xml file is as following:
<?xml version="1.0"?>
<ItemSearchErrorResponse
xmlns="http://ecs.amazonaws.com/doc/2005-10-05/">
<Error>
<Code>UnsupportedVersion</Code>
<Message>Version 2005-10-05 is unsupported. Please use 2011-08-01 or greater instead.</Message>
</Error>
<RequestId>9f95a47d-f593-4002-af01-85b1b12fdf2d</RequestId>
</ItemSearchErrorResponse>
Add a valid version to your $params array, e.g:
"ResponseGroup" => "Images,ItemAttributes,Offers",
"Version" => "2015-10-01"
I tested your script and found it works with the above.
The cause of the issue seems to be that the API defaults to a deprecated value if you omit the version parameter.
If I pass test to the q parameter of the tweets.json endpoint, it returns data fine. But if I include the # symbol at the front, i.e. #test, I get the following error:
Could not authenticate you.
The same issue happens when I use %40 instead of #.
Here is my code:
$query = array( // query parameters
'q' => '#test',
'count' => '100'
);
$method = "GET";
$path = "/1.1/search/tweets.json";
$token = 'xxxxxx';
$token_secret = 'xxxxxx';
$consumer_key = 'xxxxxxx';
$consumer_secret = 'xxxxxx';
$host = 'api.twitter.com';
$oauth = array(
'oauth_consumer_key' => $consumer_key,
'oauth_token' => $token,
'oauth_nonce' => (string)mt_rand(), // a stronger nonce is recommended
'oauth_timestamp' => time(),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_version' => '1.0'
);
$oauth = array_map("rawurlencode", $oauth); // must be encoded before sorting
$query = array_map("rawurlencode", $query);
$arr = array_merge($oauth, $query); // combine the values THEN sort
asort($arr); // secondary sort (value)
ksort($arr); // primary sort (key)
// http_build_query automatically encodes, but our parameters
// are already encoded, and must be by this point, so we undo
// the encoding step
$querystring = urldecode(http_build_query($arr, '', '&'));
$url = "https://$host$path";
// mash everything together for the text to hash
$base_string = $method."&".rawurlencode($url)."&".rawurlencode($querystring);
// same with the key
$key = rawurlencode($consumer_secret)."&".rawurlencode($token_secret);
// generate the hash
$signature = rawurlencode(base64_encode(hash_hmac('sha1', $base_string, $key, true)));
// this time we're using a normal GET query, and we're only encoding the query params
// (without the oauth params)
$url .= "?".http_build_query($query);
$url=str_replace("&","&",$url); //Patch by #Frewuill
$oauth['oauth_signature'] = $signature; // don't want to abandon all that work!
ksort($oauth); // probably not necessary, but twitter's demo does it
// also not necessary, but twitter's demo does this too
function add_quotes($str) { return '"'.$str.'"'; }
$oauth = array_map("add_quotes", $oauth);
// this is the full value of the Authorization line
$auth = "OAuth " . urldecode(http_build_query($oauth, '', ', '));
// if you're doing post, you need to skip the GET building above
// and instead supply query parameters to CURLOPT_POSTFIELDS
$options = array( CURLOPT_HTTPHEADER => array("Authorization: $auth"),
//CURLOPT_POSTFIELDS => $postfields,
CURLOPT_HEADER => false,
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false);
// do our business
$feed = curl_init();
curl_setopt_array($feed, $options);
$json = curl_exec($feed);
curl_close($feed);
return $json;
Why can't I retrieve data with the # symbol at the front of the q parameter?
As I suspected, this is a double (even triple) encoding issue. I managed to get it working in 2 separate ways (they must not be used together):
Merging before rawurlencode(), my preferred solution:
//NOPE $oauth = array_map("rawurlencode", $oauth); // must be encoded before sorting
//NOPE $query = array_map("rawurlencode", $query);
$arr = array_merge($oauth, $query); // combine the values THEN sort
$arr = array_map("rawurlencode", $arr);
asort($arr); // secondary sort (value)
ksort($arr); // primary sort (key)
Removing urldecode() after the http_build_query():
$querystring = (http_build_query($arr, '', '&'));
But at this point, I could not explain why either of them worked. It turns out that later in the code, you use this:
// this time we're using a normal GET query, and we're only encoding the query params
// (without the oauth params)
$url .= "?".http_build_query($query);
The subtle difference is that this time you did not use urldecode() after http_build_query().
Because of all the encoding that goes on, the url you end up using for the signature does not match the url you use for the request, therefore failing the authentification.
Signature
The oauth_signature parameter contains a value which is generated by running all of the other request parameters and two secret values through a signing algorithm. The purpose of the signature is so that Twitter can verify that the request has not been modified in transit, verify the application sending the request, and verify that the application has authorization to interact with the user’s account.
From Authorizing a request - Twitter Developers.
This works, the url is encoded 2 times and the signature url 3 times:
// end of $url, encoded twice
?q=%2540test&count=100
// end of $base_string, used in signature, encoded thrice
%26oauth_version%3D1.0%26q%3D%252540test
This doesn't, the url is encoded 1 time and the signature url 3 times:
// end of $url
?q=%40test&count=100
// end of $base_string, used in signature
%26oauth_version%3D1.0%26q%3D%252540test
Because the signature must only be encoded one more time than the request url, both previously mentioned solutions work (independently) because:
Solution 1 doesn't encode $query, making $url single encoded and the signature double encoded.
Solution 2 keeps the double encoding, leaving $url double encoded and making the signature triple encoded.
But wait, why does it only fail when using # ?
Because that's the only character in all the parameters that actually needs encoding. It is encoded when using http_build_query(), generating a % character that will get caught up in subsequent encodings.
I have Signed URLs on Cloudfront working fine in PHP. Bucket policies work with HTTP referrers on S3 but because Cloudfront doesn't support HTTP referrer checks I need to serve a file to one IP address only (the client that requested the file and generated the signed URL or my web server ideally).
Can someone please help me add the IP Address element to the JSON code so it works?
"IpAddress":{"AWS:SourceIp":"192.0.2.0/24"},
I'm lost with the PHP and Policy Statement but think it might be easy for someone who knows: http://tinyurl.com/9czr5lp
It does encoding/signing a bit differently for a custom policy: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html#private-content-custom-policy-statement
The below is an AWS example and works except not for the IP Address lock in.
I can test this very quickly if someone can please give me a hand for two minutes!
Thanks MASSIVELY for any help :)
Jon
function getSignedURL($resource, $timeout)
{
$keyPairId = "XXXXXXXXXXXX";
$expires = time() + $timeout;
$json = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan": {"AWS:EpochTime":'.$expires.'}}}]}';
$fp=fopen("pk-XXXXXXXX.pem","r");
$priv_key=fread($fp,8192);
fclose($fp);
$key = openssl_get_privatekey($priv_key);
if(!$key)
{
echo "<p>Failed to load private key!</p>";
return;
}
//Sign the policy with the private key
if(!openssl_sign($json, $signed_policy, $key, OPENSSL_ALGO_SHA1))
{
echo '<p>Failed to sign policy: '.openssl_error_string().'</p>';
return;
}
//Create url safe signed policy
$base64_signed_policy = base64_encode($signed_policy);
$signature = str_replace(array('+','=','/'), array('-','_','~'), $base64_signed_policy);
//Construct the URL
$url = $resource.'?Expires='.$expires.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId;
return $url;
}
$url = getSignedURL("http://s675765.cloudfront.net/filename.mp4", 600);
print $url;
{"Statement":[{"Resource":"testRes","Condition":{"DateLessThan":{"AWS:EpochTime":1357034400},"IpAddress":{"AWS:SourceIp":"192.0.2.0\/24"}}}]}
This is a valid JSON string with filled and escaped values.
If you pass the IP address as a variable make sure you escape the /
e.g.
$escapedIp = str_replace( '/', '\/', $ipAddress );
Have a look at the php json extension
This would make things quite easier:
example statement as php array
$statement = array(
'Statement' => array(
array(
'Resource' => $resource,
'Condition' => array(
'DateLessThan' => array(
'AWS:EpochTime' => $expires
),
'IpAddress' => array(
'AWS:SourceIp' => $ipAddress
)
)
)
)
);
$json = json_encode( $statement );