This is a part of a test with this wordpress plugin which is basically a license manager. I am trying to understand the internals of the system. Here is how it works.
Once the plugin is activated, it generates a private key 55d0ec3f9db414.02268045 using a simple function 'lic_verification_private_secret' => uniqid('', true). Now, when someone makes a purchase of an item eg. a wordpress plugin, a public license key 55d5d22ab70d2 is generated (using uniqid()). The public key is then sent to the customer's email id. The customer inputs that key into his site and sends a request to the license server. Below is a function about how the license manager plugin #server matches the private key with public key.
static function verify_secret_key() {
$slm_options = get_option('slm_plugin_options');
$private_secret_key = $slm_options['lic_verification_private_secret'];
$public_key = strip_tags($_REQUEST['secret_key']); //this is sent in the query string
if ($public_key == $private_secret_key) {
// send a message back to client saying the key is verified.
}
All this works, so basically where I am stumped is how the below equation is valid ? What part of the picture am I missing ?
55d5d22ab70d2 == 55d0ec3f9db414.02268045
Update - I have performed this test and it echoes false which i guess is obvious.
echo '55d0ec3f9db414.02268045' === '55d5d22ab70d2' ? 'true' : 'false';
Shared secret key:
function generate_signature($message, $secret) {
$serialized_message = serialize($message);
return md5($serialized_message . $secret);
}
$secret = "i like pie";
$content = array(
"i like" => "pie",
"pancakes" => "are also nice"
);
$message = serialize(array(
"signature" => generate_signature($content , $secret),
"content" => $content
));
// send the message
$message = unserialize($_POST["message"]);
$signature = generate_signature($message["content"], $secret);
if ($signature === $message["signature"]) {
echo "ok";
} else {
echo "you don't like pie?";
}
The secret key can be the license btw, since that's what you want to keep secret.
Related
I'm using Twilio to send a verification code through voice call. I've used this tutorial to write the code:
https://www.twilio.com/blog/implement-account-verification-login-by-phone-laravel-php
So, that's basically the code. My phone rings and I get the "You have a test account, press any key to run your code" message, but when I press a key, it hangs up on me and my code is never called.
public function sendVerificationCodeThroughCall($mobile)
{
$code = $this->createMobileCode($mobile);
$client = new Client(config('services.twilio.sid'), config('services.twilio.auth_token'));
$client->calls->create(
$mobile,
config('services.twilio.phone_number'),
["url" => route('voice-code.build', $code)]
);
}
public function buildVoiceCode($code)
{
Log::debug('Twilio called to say: ' . $code);
$code = $this->splitCode($code);
$response = new VoiceResponse();
$response->say("Salaam. This is your verification code: {$code}. I repeat, {$code}.");
echo $response;
}
protected function splitCode($code)
{
return implode('.', str_split($code));
}
I'm trying to validate a Google Play/Android In App Product consumed purchase server-side using PHP. I get a response back with a valid receipt, but there are two confusing issues:
The productId is always null
If I change the the $productId in the sample below to an invalid ID, it will return the exact same response. This seems to be the case for literally any string.
Here is my sample code:
$purchaseToken = 'TEST purchaseToken FROM ANDROID APP';
$appId = 'com.example.myapp';
$productId = 'com.example.myapp.iap1';
$googleClient = new \Google_Client();
$googleClient->setScopes([\Google_Service_AndroidPublisher::ANDROIDPUBLISHER]);
$googleClient->setApplicationName($appId);
$googleClient->setAuthConfig(__DIR__ . '/gp-service.json');
$googleAndroidPublisher = new \Google_Service_AndroidPublisher($googleClient);
$purchase = $googleAndroidPublisher->purchases_products->get($appId, $productId, $purchaseToken);
If I dump out $purchase, I get:
=> Google_Service_AndroidPublisher_ProductPurchase {
+acknowledgementState: 1,
+consumptionState: 1,
+developerPayload: "",
+kind: "androidpublisher#productPurchase",
+obfuscatedExternalAccountId: null,
+obfuscatedExternalProfileId: null,
+orderId: "GPA.XXXX-XXXX-XXXX-XXXXX",
+productId: null,
+purchaseState: 0,
+purchaseTimeMillis: "1602771022178",
+purchaseToken: null,
+purchaseType: 0,
+quantity: null,
+regionCode: "US",
}
Does anyone know what I am doing wrong here? It doesn't seem to be validating the productId on its end nor does it provide me the data I would need to validate it on my end, meaning I have no way of validating this IAP right now.
https://issuetracker.google.com/issues/169870112
I've found on google issue tracker that ignoring productId is an intended behaviour. I've wrote post asking if the null that we receive in response is also an intended behaviour. I hope not because as you wrote if productId will be always null we have no way of fully validating IAP right now.
$purchaseToken was undefined in Josh's answer.
<?php
$appId = "com.example.myapp";
$serviceAccountJson = __DIR__ . "/gp-service.json";
// this is the raw receipt data received on the device from Google Play; this example is obfuscated and only shows the keys for sensitive fields
$googlePlayReceipt =
'{"productId": "com.example.myapp.iap1","transactionDate": 1602714720893,"transactionReceipt": "","purchaseToken": "","transactionId": "","dataAndroid": "","signatureAndroid": "","isAcknowledgedAndroid": false,"autoRenewingAndroid": false,"purchaseStateAndroid": 1}';
// decode the json to an array we can use
$decodedGooglePlayReceipt = json_decode($googlePlayReceipt, true);
// the data that was signed for verification purposes
$data = $decodedGooglePlayReceipt["transactionReceipt"];
// the signature that was used to sign the $data
$signature = $decodedGooglePlayReceipt["signatureAndroid"];
// The "Base64-encoded RSA public key" for your app, taken from the Google Play Console
// In the Classic Console: Your App -> Development Tools -> Services & APIs -> Licensing & in-app billing
// In the New Console: Your App -> Monetize -> Monetization Setup -> Licensing
$base64EncodedPublicKeyFromGoogle = "################";
// Convert the key into the normal public key format
// Just need to split the base64 key into 64 character long lines and add the usual prefix/suffix
$openSslFriendlyKey =
"-----BEGIN PUBLIC KEY-----\n" .
chunk_split($base64EncodedPublicKeyFromGoogle, 64, "\n") .
"-----END PUBLIC KEY-----";
// Convert the key to the openssl key ID that openssl_verify() expects
// I'm unsure if this step would be necessary on all platforms
$publicKeyId = openssl_get_publickey($openSslFriendlyKey);
// Use openssl_verify() to verify the $signature (which has to be base64 decoded!) against the $data using the public key we have
$result = openssl_verify(
$data,
base64_decode($signature),
$publicKeyId,
OPENSSL_ALGO_SHA1
);
if ($result === 0) {
throw new Exception("Invalid receipt");
}
if ($result !== 1) {
throw new Exception(openssl_error_string());
}
// receipt data is valid. now let's grab the productId and purchaseToken
$decodedData = json_decode($data, true);
$purchasedProductId = decodedData["productId"];
$purchaseToken = decodedData["purchaseToken"];
// now we'll verify that the receipt is valid for our account
try {
$googleClient = new \Google_Client();
$googleClient->setScopes([
\Google_Service_AndroidPublisher::ANDROIDPUBLISHER,
]);
$googleClient->setApplicationName($appId);
$googleClient->setAuthConfig($serviceAccountJson);
$googleAndroidPublisher = new \Google_Service_AndroidPublisher(
$googleClient
);
$purchase = $googleAndroidPublisher->purchases_products->get(
$appId,
$purchasedProductId,
$purchaseToken
);
} catch (Throwable $exception) {
// this means the receipt data is unable to be validated by Google
throw new Exception("Invalid receipt");
}
After seeing Deusald's response here and starting to type up a response to the Google Issues ticket complaining about it, I had an epiphany: Google is just validating that the transaction is valid for your account and expects you to validate the receipt data server-side. They include a base64 encoded RSA SHA1 signature in the receipt data and the original data they used to create that signature, so they give you everything you need to accomplish this.
The below snippet accomplishes that verification for PHP, but it should be easily portable to other languages:
<?php
// our app's bundle id
$appId = 'com.example.myapp';
// the location of a Service Account JSON file for a Service account that has access to the "Financial Data" permissions in the Play Console
$serviceAccountJson = __DIR__ . '/gp-service.json';
// this is the raw receipt data received on the device from Google Play; this example is obfuscated and only shows the keys for sensitive fields
$googlePlayReceipt = '{"productId": "com.example.myapp.iap1","transactionDate": 1602714720893,"transactionReceipt": "","purchaseToken": "","transactionId": "","dataAndroid": "","signatureAndroid": "","isAcknowledgedAndroid": false,"autoRenewingAndroid": false,"purchaseStateAndroid": 1}';
// decode the json to an array we can use
$decodedGooglePlayReceipt = json_decode($googlePlayReceipt, true);
// the data that was signed for verification purposes
$data = $decodedGooglePlayReceipt['transactionReceipt'];
// the signature that was used to sign the $data
$signature = $decodedGooglePlayReceipt['signatureAndroid'];
// The "Base64-encoded RSA public key" for your app, taken from the Google Play Console
// In the Classic Console: Your App -> Development Tools -> Services & APIs -> Licensing & in-app billing
// In the New Console: Your App -> Monetize -> Monetization Setup -> Licensing
$base64EncodedPublicKeyFromGoogle = '################';
// Convert the key into the normal public key format
// Just need to split the base64 key into 64 character long lines and add the usual prefix/suffix
$openSslFriendlyKey = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($base64EncodedPublicKeyFromGoogle, 64, "\n") . "-----END PUBLIC KEY-----";
// Convert the key to the openssl key ID that openssl_verify() expects
// I'm unsure if this step would be necessary on all platforms
$publicKeyId = openssl_get_publickey($openSslFriendlyKey);
// Use openssl_verify() to verify the $signature (which has to be base64 decoded!) against the $data using the public key we have
$result = openssl_verify($data, base64_decode($signature), $publicKeyId, OPENSSL_ALGO_SHA1);
if ($result === 1) {
// receipt data is valid. now let's grab the productId and purchaseToken
$decodedData = json_decode($data, true);
$purchasedProductId = decodedData['productId'];
$purchaseToken = decodedData['purchaseToken'];
// now we'll verify that the receipt is valid for our account
try {
$googleClient = new \Google_Client();
$googleClient->setScopes([\Google_Service_AndroidPublisher::ANDROIDPUBLISHER]);
$googleClient->setApplicationName($appId);
$googleClient->setAuthConfig($serviceAccountJson);
$googleAndroidPublisher = new \Google_Service_AndroidPublisher($googleClient);
$purchase = $googleAndroidPublisher->purchases_products->get($appId, $purchasedProductId, $purchaseToken);
} catch (Throwable $exception) {
// this means the receipt data is unable to be validated by Google
throw new Exception('Invalid receipt');
}
} elseif ($result === 0) {
throw new Exception('Invalid receipt');
} else {
throw new Exception(openssl_error_string());
}
I am using twitter account activity api's to send/receive messages. In it i am facing problem in generating webhook. First time it was created successfully. In webhook file i am saving messages in database when each time message sent or received. But when I sent message nothing goes in database. Here is webhook file:
const APP_CONSUMER_SECRET = '**********';
// Example token provided by incoming GET request
if(isset($_REQUEST['crc_token'])) {
$token = $_REQUEST['crc_token'];
/**
* Creates a HMAC SHA-256 hash created from the app TOKEN and
* your app Consumer Secret.
* #param token the token provided by the incoming GET request
* #return string
*/
function get_challenge_response($token) {
$hash = hash_hmac('sha256', $token, APP_CONSUMER_SECRET, true);
$response = array(
'response_token' => 'sha256=' . base64_encode($hash)
);
return json_encode($response);
}
}else{
$feedData = file_get_contents('php://input');
$handleData = fopen('twitterDemo.txt', "w" );
fwrite($handleData,$feedData);
fclose($handleData);
$array = json_decode(file_get_contents('php://input'), true);
if (isset($array['direct_message_events'][0]['type']) && $array['direct_message_events'][0]['type'] == 'message_create' ) {
include_once ('config.php');
include_once ('database-queries.php');
$message = $array['direct_message_events'][0]['message_create']['message_data']['text'];
$sender = $array['direct_message_events'][0]['message_create']['sender_id'];
$from = $array['direct_message_events'][0]['message_create']['target']['recipient_id'];
$message_type = 'incoming';
$message_status = 'unread';
$userId = $sender;
$account_name = 'twitter';
$image_url = '';
if(isset($array['direct_message_events'][0]['message_create']['message_data']['attachment'])){
$image_url = "Not Image";
}
$data = array('to'=>$from, 'from'=>$sender, 'msg'=>$message,'image_url' =>$image_url);
insert($data, $account_name, $message_type, $message_status, $conn);
}
}
I thought there might be webhook problem so i deleted the existing app and create new one and set development environment label for it with new name. But for it when i tried to create webhook it gives me error:
[code] => 214 [message] => Webhook URL does not meet the requirements.
Invalid CRC token or json response format.
I dont know whats happening here now. I am using this api to create webhook url
$url = "https://example.com/twitter/webhook.php";
$result = $connection->post("account_activity/all/env_name/webhooks", ["url" => $url]);
Can anyone please help me this out. Any help will be appreciated.
Thanks!
I ran into the same error message.
The problem seem to be that the
if(isset($_REQUEST['crc_token']))
is not working anymore. Though the token is set. I don't know what is causing the problem but when I let the code just fetch the $_REQUEST['crc_token'] and create and print the hash its working.
But I see your code is not calling the get_challenge_response($token) function.
I think
print get_challenge_response($token)
may help? If the Webhook was deactivated by twitter you have to initiate another CRC check:
https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/subscribe-account-activity/guides/securing-webhooks
Your app can also trigger a CRC when needed by making a PUT request with your webhook id. Triggering a CRC is useful as you develop your webhook application, after deploying new code and restarting your service.
I am just learning about JWT and Authentication in general, its my first real look inside this world so Its all abit overwhelming.
I wanted to add login/general sessions for my new site (Angular 2 SPA), and after abit of research JWT seemed to be the best approach, so I got php-jwt and setup a super basic authentication with my basic database shown here:
class userAuth {
// create an empty id variable to hold the user id
private $id;
private $email;
private $key = "16FD8C979FC40CCB97457F4AD79B32A73758771B4D1943C379FB3266EECE0C3E";
// Checks if the user exists in the database
private function validUser($email, $password) {
$conn = new mysqli(DBSERVER, DBUSERNAME, DBPASSWORD, DBNAME);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$truepassword = hash('sha256', $password); // password hashing using SHA256
$query = $conn->query("select * from users where ( username='$email' OR email = '$email') and password='$truepassword'");
$count = mysqli_num_rows($query);
if($count == 1) {
$row = mysqli_fetch_array($query);
$this->id = $row['id'];
$this->email = $row['email'];
return true;
}else{
return false;
}
}
private function genJWT() {
// Make an array for the JWT Payload
$payload = array(
"id" => $this->id,
"email" => $this->email,
"exp" => time() + (60 * 60)
);
// encode the payload using our secretkey and return the token
return JWT::encode($payload, $this->key);
}
public function checkUser($email, $password) {
// check if the user exists
if ($this->validUser($email, $password)) {
// generate JSON web token and store as variable
$token = $this->genJWT();
$resultJSON = array(
'email' => $this->email,
'token' => $token
);
return json_encode($resultJSON);
} else {
return 'We Couldn\'t Find You In Our Database. Maybe Wrong Email/Password Combination';
}
}
private function validJWT($token) {
$res = array(false, '');
// using a try and catch to verify
try {
//$decoded = JWT::decode($token, $this->key, array('HS256'));
$decoded = JWT::decode($token, $this->key, array('HS256'));
} catch (Exception $e) {
return $res;
}
$res['0'] = true;
$res['1'] = (array) $decoded;
return $res;
}
public function validLogin($token) {
// checks if an email is valid
$tokenVal = $this->validJWT($token);
// check if the first array value is true
if ($tokenVal['0']) {
// create user session and all that good stuff
return "Everything went well, time to serve you what you need.";
} else {
return "There was an error validating your email. Send another link";
}
}
}
And this was good to get my head around the idea of JWT auth, I managed (after hours mind you) to collect the token and save it to local storage on a successful login. but I tried to use a jwt library to properly manage getting info with my token etc I couldnt get it to work, beccause I imagine my setup doesnt provide what the lib expects the token to be formatted or something along those lines
Would it be a good idea to try and just continue and learn as I go in this manner, because at the moment I havnt found many resources or tutorials on building my own JWT backend, or how I would talk to said backend correctly.
I have now setup a basic auth0 account and im looking into/testing that usage, would I be better off learning on my own, or using a lib (happy to be referred to, to other JWT API's that work with angular 2, or would be appropriate for it).
I also dont like the idea of my server backend being someone else's if that makes sense.
When I sign in to StackOverflow using Google, I get the following message:
Stackoverflow.com is asking for some information from your Google Account example#gmail.com
• Email address: example#gmail.com
However, on my own site, when I log in with OpenID I can't ask for the e-mail address. Instead, I get this message:
You are signing in to example.com with your Google Account example#gmail.com
I am also finding it difficult to understand at what step I need to request the e-mail address. Here is the code that I think the step should be built into:
/**
* Authenticates the given OpenId identity.
* Defined by Zend_Auth_Adapter_Interface.
*
* #throws Zend_Auth_Adapter_Exception If answering the authentication query is impossible
* #return Zend_Auth_Result
*/
public function authenticate() {
$id = $this->_id;
$consumer = new Auth_OpenID_Consumer($this->_storage);
if (!empty($id)) {
$authRequest = $consumer->begin($id);
if (is_null($authRequest)) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE,
$id,
array("Authentication failed", 'Unknown error')
);
}
if (Auth_OpenID::isFailure($authRequest)) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE,
$id,
array("Authentication failed", "Could not redirect to server: " . $authRequest->message)
);
}
$redirectUrl = $authRequest->redirectUrl($this->_root, $this->_returnTo);
if (Auth_OpenID::isFailure($redirectUrl)) {
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE,
$id,
array("Authentication failed", $redirectUrl->message)
);
}
Zend_OpenId::redirect($redirectUrl);
} else {
$response = $consumer->complete(Zend_OpenId::selfUrl());
switch($response->status) {
case Auth_OpenID_CANCEL:
case Auth_OpenID_FAILURE:
return new Zend_Auth_Result(
Zend_Auth_Result::FAILURE,
null,
array("Authentication failed. " . #$response->message)
);
break;
case Auth_OpenID_SUCCESS:
return $this->_constructSuccessfulResult($response);
break;
}
}
}
This seems like it should be so obvious... but I'm having a hard time Googling it and combing through the code to figure it out. Any help would be greatly appreciated!
You can ask for an email address using Zend Simple Registration Extension
$sreg = new Zend_OpenId_Extension_Sreg(array(
'nickname'=>true,
'email'=>false,
'fullname'=>false), null, 1.1);
$consumer = new Zend_OpenId_Consumer();
if (!$consumer->login($_POST['openid_identifier'],
'example-6_3.php',
null,
$sreg)) {
die("OpenID login failed.");
}
If you want to use Janrain library, you can add extensions to the request like this:
$sreg_request = Auth_OpenID_SRegRequest::build(
// Required
array('nickname'),
// Optional
array('email'));
if ($sreg_request) {
$authRequest->addExtension($sreg_request);
}
Have a look at the consumer example: https://github.com/openid/php-openid/blob/master/examples/consumer/