I am working on php socket server side using ssl security layer, It is working fine from php client side, now i want to connect my server with android client with ssl security layer. I am not getting any helpful solution which can connect with my php server through android client with ssl Authentication
My PHP server.php side code is given below
<?php
$ip="000.000.00.00"; //Set the TCP IP Address to listen on
$port="****"; //Set the TCP Port to listen on
$pem_passphrase = "password"; //Set a password here
$pem_file = "filename.pem"; //Set a path/filename for the PEM SSL Certificate which will be created.
$forkedSocket;
//The following array of data is needed to generate the SSL Cert
$pem_dn = array(
"countryName" => "IN", //Set your country name
"stateOrProvinceName" => "State", //Set your state or province name
"localityName" => "City", //Set your city name
"organizationName" => "Orgnization", //Set your company name
"organizationalUnitName" => "unit", //Set your department name
"commonName" => "hostname", //Set your full hostname.
"emailAddress" => "email#email.com" //Set your email address
);
//create ssl cert for this scripts life.
echo "Creating SSL Cert\n";
createSSLCert($pem_file, $pem_passphrase, $pem_dn);
//setup and listen to a tcp IP/port, returning the socket stream
echo "Listening to {$ip}:{$port} for connections\n";
$socket = setupTcpStreamServer($pem_file, $pem_passphrase, $ip, $port);
while(true){
//Accept any new connections
$forkedSocket = stream_socket_accept($socket, "-1", $remoteIp);
echo "New connection from $remoteIp\n";
//start SSL on the connection
stream_set_blocking ($forkedSocket, true); // block the connection until SSL is done.
stream_socket_enable_crypto($forkedSocket, true, STREAM_CRYPTO_METHOD_SSLv3_SERVER);
//enter a loop until an exit command is received.
$exit=false;
$i=1;
while($exit==false) {
//Read the command from the client. This will read 8192 bytes of data, If you need to read more you may need to increase this. However some systems will fragment the command over 8192 anyway, so you would instead need to write a loop waiting for the command input to end before proceeding.
$command = fread($forkedSocket, 8192);
echo "f_read success--".$command;
//unblock connection
stream_set_blocking ($forkedSocket, false);
//run a switch on the command to determine what we need to do
switch($command) {
CASE "exit";
$exit=true;
echo "exit command received \n";
fwrite($forkedSocket, "exit.... {$remoteIp}. -- $i finished connection"
);
//close the connection to the client
fclose($forkedSocket);
break;
default ;
//write back to the client a response.
fwrite($forkedSocket, "Hello {$remoteIp}. This is our $i command run!".$command );
$i++;
if($i==25){
$exit=true;
}
echo "command received \n";
break;
}
}
}
function createSSLCert($pem_file, $pem_passphrase, $pem_dn) {
//create ssl cert for this scripts life.
//Create private key
$privkey = openssl_pkey_new();
//Create and sign CSR
$cert = openssl_csr_new($pem_dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365);
//Generate PEM file
$pem = array();
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
$pem = implode($pem);
//Save PEM file
file_put_contents($pem_file, $pem);
chmod($pem_file, 0600);
}
function setupTcpStreamServer($pem_file, $pem_passphrase, $ip, $port) {
//setup and listen to a tcp IP/port, returning the socket stream
//create a stream context for our SSL settings
$context = stream_context_create();
//Setup the SSL Options
stream_context_set_option($context, 'ssl', 'local_cert', $pem_file); // Our SSL Cert in PEM format
stream_context_set_option($context, 'ssl', 'passphrase', $pem_passphrase); // Private key Password
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
//create a stream socket on IP:Port
$socket = stream_socket_server("tcp://{$ip}:{$port}", $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);
stream_socket_enable_crypto($socket, false);
return $socket;
}
?>
and My Android CLient side code is
SocketFactory sf = SSLSocketFactory.getDefault();
SSLSocket socket = null;
// socket = (SSLSocket)
socket = (SSLSocket) sf.createSocket("000.000.00.00", 0000);
socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
#Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
Log.e("logMessage", "handshake completed...");
}
});
if (socket.getEnableSessionCreation()) {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
SSLSession s = socket.getSession();
if (!hv.verify("000.000.00.00", s)) {
Log.e("logMessage", "Failed....");
// throw new SSLHandshakeException("Expected mail.google.com, found " + s.getPeerPrincipal());
throw new SSLHandshakeException("Expected 000.000.00.00, found " + s.getPeerPrincipal());
} else {
//Toast.makeText(context, "verified", Toast.LENGTH_SHORT).show();
Log.e("logMessage", "verified...");
}
}else
{
Log.e("logMessage", "socket.getEnableSessionCreation() -- failed...");
}
// At this point SSLSocket performed certificate verification and
// we have performed hostname verification, so it is safe to proceed.
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
Related
I have write a simple php script to test the apple push notification on my local machine and here it is:
<?php
function sendPushNotification($token, $payload) {
$cert = 'FooBarDev.pem';
$passphrase = '<here is my passphrase>';
$endpoint = 'ssl://gateway.sandbox.push.apple.com:2195';
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', $cert);
stream_context_set_option($context, 'ssl', 'passphrase', $passphrase);
stream_context_set_option($context, 'ssl', 'cafile', 'entrust_2048_ca.cer');
$conn = stream_socket_client($endpoint, $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $context);
if (!$conn) {
echo('Connection to APNs failed');
return;
}
$msg = chr(0) . pack('n', 32) . pack('H*', $token) . pack('n', strlen($payload)) . $payload;
$result = fwrite($conn, $msg, strlen($msg));
fclose($conn);
if ($result) {
echo('Notification sent');
} else {
echo('Error sending notification');
}
}
$dbc = mysqli_connect('localhost', 'root', '<here is database password>', 'foobar');
$query = "SELECT token FROM tokens";
$result = mysqli_query($dbc, $query);
$aps['alert'] = $_POST['alert'];
$aps['badge'] = intval($_POST['badge']);
$aps['sound'] = 'default';
$payload['aps'] = $aps;
while ($row = mysqli_fetch_array($result)) {
sendPushNotification($row['token'], json_encode($payload));
}
?>
and below is some client side code:
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
let token = String(deviceToken).stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "<>")).stringByReplacingOccurrencesOfString(" ", withString: "").stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
let body = "token=\(token)"
let endpoint = "http://120.25.88.119/foobar/register_token.php"
let request = NSMutableURLRequest(URL: NSURL(string: endpoint)!, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 10)
request.HTTPMethod = "POST"
request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config, delegate: nil, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request) {
data, response, error in
}
task.resume()
}
I made a request to the localhost with some post data and it worked just fine, I received notification on iPhone and iPad.
But when I upload the php script along with the certificate files, and send another request to the remote server, I got no notifications.
And here is something I have to state:
1: I have change the database query part of the php code, so it's not likely that I was querying the wrong database, fetching the wrong token, and there is tokens too in the remote database.
2: I also uploaded the certificates altogether with php files, same directory structure as on my mac.
3: I have changed the file mode to 777 both on local machine and server.
Could anybody help me? Appreciate!
I use Push Notifs in my PHP Laravel app. I created a pem file and tested it. When using it on my dev machine, it correctly pushes to the mobile device. When I now push the whole project to my production server, and start the pushnotif call, I get the error: Failed to enable crypto
Do I need to create a special pem file while being on production server? And I am not talking about "production certificates" I still want to use the sandbox for testing
<?php
namespace App\Helpers;
use Carbon\Carbon;
use Illuminate\Support\Facades\Config;
class PushNotificationHelper {
public function pushToResponder($deviceToken) {
// Set device token of mobile device and the passphrase for certification
$pushToken = $deviceToken;
$passphrase = Config::get('MFConfig.PushNotificationTest.passPhrase');
$APNS = Config::get('MFConfig.PushNotificationTest.APNS');
// Open new context for streaming and set certificate as well as passphrase
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '../App/Certificates/ck.pem');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
stream_context_set_option($ctx, 'ssl', 'cafile', '../App/Certificates/entrust_2048_ca.cer');
// Open connection to APNS
$fp = stream_socket_client($APNS, $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
// If no connection could be made then fail with error, otherwise connect
if (!$fp) {
exit("Failed to connect: $err, $errstr" . PHP_EOL);
}
echo 'Connected to APNS' . PHP_EOL;
// Create the payload body
$body['aps'] = array(
'alert' => "MEDIFAKTOR Einsatz",
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $pushToken) . pack('n', strlen($payload)) . $payload;
// Send to server
$result = fwrite($fp, $msg, strlen($msg));
// If no result is available, the message was not delivered.
if(!$result) {
echo 'Message not delivered.' . PHP_EOL;
} else {
echo 'Message successfully delivered.' . PHP_EOL;
}
// Close connection
fclose($fp);
}
}
I tested the connection with:
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert ck.pem -debug -showcerts -CAfile entrust_2048_ca.cer
and it returns status 0 (ok) which is fine I think?
but when I call the function I get: failed loading cafile stream
Not sure about this, but it might be your php settings in the dev enviroment.
try to locate the cacert.pem file.
If you are using a linux system you can try locate cacert.pem using the terminal.
When you find the location find the php.ini file and find this line:
;openssl.cafile =
and change this to
openssl.cafile= path from locate cacert.pem command or the location the .pem is actually located.
Report back on how it went.
There is a PHP Websocket client: https://github.com/symbiose/php-websocket-client repository. And it works fine for not-secured connection.
What I need is to connect to a websocket server from php code through secure connection. To implement this task I took the code above and modified it a bit. The connect() method of WebSocketClient now looks like this in my code:
public function connect()
{
$root = $this;
if ($this->getPort() == 443) {
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$client = stream_socket_client("tls://{$this->getHost()}:{$this->getPort()}", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context);
} else {
$client = stream_socket_client("tcp://{$this->getHost()}:{$this->getPort()}", $errno, $errstr);
}
if (!$client) {
throw new RuntimeException('Cannot connect to socket ([#'.$errno.'] '.$errstr.')');
}
$this->setSocket(new Connection($client, $this->getLoop()));
$this->getSocket()->on('data', function ($data) use ($root) {
$data = $root->parseIncomingRaw($data);
$root->parseData($data);
});
$this->getSocket()->write($this->createHeader());
return $this;
}
As a result I almost managed to connect to a server. Websocket server saw my connection, replied that everything is ok and wrote it to the log.
But unfortunately, the library doesn't understand, that it has successfully connected to the server.
This "if":
if (base64_encode(pack('H*', sha1($this->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) === $response['Sec-Websocket-Accept']) {
$this->connected = true;
}
never gets executed. And so I can't establish the connection.
I can also confirm, that if we use 80 port - everything works like a charm.
So if you have any ideas on why this can happen, i"d really really appreciate them. Cause I've already ran out of ideas.
Best regards,
Kostya
Apple's docs say that:
"If you send a notification and APNs finds the notification malformed or otherwise unintelligible, it returns an error-response packet prior to disconnecting. (If there is no error, APNs doesn’t return anything.) Figure 5-3 depicts the format of the error-response packet."
This leads me to believe that the only reason that APNS would not send something back is if what I sent them was in the correct format. However when I try to fread for their response I get a string of 0 length and when unpacked becomes null, which I assume means nothing has been written back to me.
I the stream was opened by stream_socket_client() and did not return false or throw an exception. I know that my fwrite successfully wrote 154 bytes to that stream as well. Why would there be no response from Apple?
Here is the code for connecting to APNS:
function openConnection() {
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $this->APNS_CERT);
stream_context_set_option($streamContext, 'ssl', 'passphrase', $this->Password);
$apns = stream_socket_client('ssl://' . $this -> APNS_HOST . ':' . $this -> APNS_PORT, $error, $errorString, 60, STREAM_CLIENT_CONNECT, $streamContext);
return $apns;
}
Then, in the next method after calling openConnection:
//open the connection to use with apple.
$apns = $this->openConnection();
$alert = $message['alert'];
$badge = $message['badge'];
$deviceToken = $message['deviceToken'];
$payload['aps'] = array(
'alert' => $message['alert'],
'badge' => $message['badge'],
'sound' => $message['sound']
);
if ($message['extraPayload'] != null) {
$payload['acme'] = $message['extraPayload'];
}
$encodedString = json_encode($payload);
//create message
$apnsMessage = chr(1) . pack("N", $message['identifier']) . pack("N", $message['expire']) . pack("n", 32) . pack('H*', str_replace(' ', '', $message['deviceToken'])) . pack("n",strlen($encodedString)) . $encodedString;
$write = fwrite($apns, $apnsMessage);
echo $write;
//the echo was just to see if it wrote.
if (!$apns) {
socket_close($apns);
fclose($apns);
echo "connection to APNS was lost.";
}
//look for changes. $null=null because some bug doesn't just let you pass null.
$null = null;
$changedStreams = stream_select($streamArray, $null, $null, 0, 1000000);
//check if it is actually false
if ($changedStreams === false) {
//close stream when done.
socket_close($apns);
fclose($apns);
echo "No response from APNs";
} elseif ($changedStreams > 0) {
//then check if what they sent back is an error and grab the error packet
$responseBinary = fread($apns, 6);
var_dump($responseBinary);
//check that it's the right thing
if ($responseBinary != false || strlen($responseBinary) == 6) {
//convert it from it's binary stream state and print.
$response = unpack('Ccommand/Cstatus_code/Nidentifier', $responseBinary);
var_dump($response);
//close stream when done.
socket_close($apns);
fclose($apns);
}
} else {
echo "Apple failed to respond, message was not sent.";
}
The var_dump at the end is NULL.
Edit:
Turns out it was an error with conflicting credentials. It was solved by creating a new pem file.
From that page:
You should regularly connect with the feedback web server and fetch the current list of those devices that have repeatedly reported failed-delivery attempts. Then you should cease sending notifications to the devices associated with those applications. See “The Feedback Service” for more information.
Feedback Service
Access to the feedback service takes place through a binary interface similar to that used for sending push notifications. You access the production feedback service via feedback.push.apple.com, port 2196; you access the sandbox feedback service via feedback.sandbox.push.apple.com, port 2196. As with the binary interface for push notifications, you must use TLS (or SSL) to establish a secured communications channel. The SSL certificate required for these connections is the same one that is provisioned for sending notifications. To establish a trusted provider identity, you should present this certificate to APNs at connection time using peer-to-peer authentication.
For the last few days, we are experiencing some strange behaviour with PHP when using sockets to connect to APN servers on our production server.
For most of the times, the payload is pushed without any errors and the client receives the notification. However on some cases we start receiving a PHP error (even though we are receiving an error, sometimes the notification is pushed). When we start to see this error, it continues for hours then disappears and PHP continues to work like nothing has happened.
Another strange thing is that, running the same PHP code from shell produces no errors whatsoever. Running it from web (nginx / php-fpm) does... PHP running on shell and web have the same configuration and share the same php.ini. The only difference is web is running on php-fpm.
Also, the same code + certificate runs on our staging server without any errors. Production server is a copy of the staging server, so every configuration is same.
We were able to find a few answers to what might be causing this error, including answers from stackoverflow.com, but we were not able to find a solution or solve it.
Notifications to Apple servers are sent one by one, and not as a bundle. But we are not making too many connections (a thousand a day maybe). There is no queue system.
So, in short
We are sometimes receiving a PHP error while sending our notifications, but not always.
Sending notifications from shell via same PHP does not produce any errors
Sending notifications from staging server does not produce any errors
We tried these
Recreating the certificate and key
Recreating the PEM file
Changing ssl:// to sslv3://
Using stream_socket_client
Using fsockopen
Changing/removing certificate password
The error is:
2012/08/28 12:18:09 [error] 4282#0: *225858 FastCGI sent in stderr:
"PHP message: PHP Warning: fwrite() [<a href='function.fwrite'>function.fwrite</a>]:
SSL operation failed with code 1. OpenSSL Error messages:
error:1409F07F:SSL routines:func(159):reason(127) in
/usr/local/nginx/html/play/classes/PushNotification.php on line 283"
while reading response header from upstream, client: 94.---.---.---,
server: play.--------.com, request: "POST /game_request_random.php HTTP/1.1",
upstream: "fastcgi://unix:/var/run/phpfpm.sock:",
host: "play.--------.com", referrer: "http://--------.com/"
The code connecting and sending the payload from the php is actually part of a class, this part is what makes the connection and sends the payload:
private function ConnectAndSend ( $msg = false ) {
$ctx = stream_context_create();
stream_context_set_option( $ctx, 'ssl', 'local_cert', $this->certificate );
stream_context_set_option( $ctx, 'ssl', 'passphrase', $this->certificatepass );
// Open a connection to the APNS server
$fp = stream_socket_client( APN_SERVER, $err, $errstr, 60, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, $ctx );
if ( !$fp ) {
errorlog( "Push notification error : $err $errstr" );
$this->error = "$err $errstr";
return;
}
// Build the notification
if ( !$msg ) {
$msg = chr( 0 ) . pack( 'n', 32 ) . pack( 'H*', $this->devicetoken ) . pack( 'n', strlen( $this->payload ) ) . $this->payload;
}
// Send it to the server
if ( !($result = fwrite( $fp, $msg, strlen( $msg ) )) ) {
// Could not send
$this->error = 'Notification could not be send';
errorlog( "Push notification error : {$this->error}" );
} else {
// Notification sent
$this->error = false;
errorlog( "Push notification sent" );
}
fclose($fp);
// Reset the content
$this->devicetoken = false;
$this->message = false;
$this->command = false;
$this->badge = 0;
$this->payload = false;
$this->sound = false;
}
stream_socket_connection is the 283rd line appearing in the error message
We are not using the sandbox (sslv3://gateway.push.apple.com:2195)
PHP version is 5.3.15
Is this a PHP or OpenSSL bug that we don't know of? Any ideas what and where to check?
Does Apple have a site where we can check the current health of the APN network?
Any help is greatly appreciated...
Thanks
Check out this code to send multiple messages
$i = 0;
while($res = mysql_fetch_array( $result )) {
$deviceTokens[$i] = $res['token'];
$i++;
}
// APNs Push testen auf Token
//$deviceToken = $token; // Hier das Device-Token angeben, ist 64-stellig
// Payload erstellen und JSON codieren
$message = $_POST['message'];
$message = utf8_encode($message);
$payload['aps'] = array('alert' => 'Neuer Artikel in Aktuelles', 'badge' => +1, 'sound' => 'default');
if (trim($message) != '') {
$payload['aps'] = array('alert' => "$message", 'badge' => 1, 'sound' => 'default');
}
$payload = json_encode($payload);
//Development: $apnsHost = 'gateway.sandbox.push.apple.com';
$apnsHost = 'gateway.push.apple.com';
$apnsPort = 2195;
//Development: $apnsCert = 'apsDevBundle.pem';
$apnsCert = 'apns-dev.pem';
// Stream erstellen
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);
$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
if ($error==0)
{
for($i = 0; $i<count($deviceTokens); $i++) {
// Build the binary notification
$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceTokens[$i])) . chr(0) . chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);
}
// Verbindung schliessen
fclose($apns);
}
else
{
var_dump($error);
var_dump($errorString);
die("Fehler aufgetreten.");
}