I'm trying to connect to a third party API and I'm using the Zend 1.7.0 framework to access it. To authenticate, I have to first check an SSL certificate returned from a URL. This is what I'm doing in my code.
$client = new Zend_Http_Client($url);
$response = $client->request('GET');
The response is always OK, and when I navigate to '$url' I can see the certificate if I click on the lock in the bottom right corner of my browser window.
Is there a way to access the certificate's owner, expiration date and other properties via the response object? Do I have to do anything special to the request before sending it to obtain the information about the certificate?
I don't think it's possible with the standard connection adaptor that Zend_Http_Client uses. I did a bit of digging and it looks like the adaptor uses fsockopen which pretty much hide what you're looking for. The way to do it is to open the socket yourself and get the certificate first:
$url = 'mail.google.com'; // For example
$context = stream_context_create();
$res = stream_context_set_option($context, 'ssl', 'capture_peer_cert', true);
$res = stream_context_set_option($context, 'ssl', 'verify_host', true);
if ($socket = stream_socket_client("tls://$url:443/", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $context)) {
if ($options = stream_context_get_options($context)) {
if (isset($options['ssl']) && isset($options['ssl']['peer_certificate'])) {
$keyinfo = openssl_x509_parse($options[$wrapper]['peer_certificate']);
var_dump($keyinfo);
}
}
fclose($fp);
}
Related
I am having issue with web socket connection/handshake from Firefox to my server written in Php.
Problem is when I try to connect client(using js/php or even websocket.org/echo.html) I get issue and the issue is client is firstly connected and then disconnected automatically. And at server, I am using fwrite to to send message to client. And i have also noticed that for a client from firefox fwrite returns 0 as a response, but at client side I can see server has posted handshake data.
Now I see problem is here, when server is writing but response is 0 and still browser(firefox) is getting response but it gets disconnected with the server at the same time. I even tried to compare(calculate) data in bytes, which server is posting to client. I see its exactly same.
Note:-
Just to mention here, same php server script is working fine for Opera/Chrome/Safari browsers.
Below are the things which are involved in this issue;
Firefox [71.0]
Php [7.0]
Apache
Let's Encrypt SSL
# STREAM SOCKET CREATION OVER SSL(WSS)
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'local_cert', 'ssl-test.pem'); #change to your own ssl.pem path
stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);
$sock = stream_socket_server("ssl://".HOST_NAME.":".PORT,$errno,$errstr,STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context) or die('Unable to start server, seems server is already running!');
// stream_set_blocking($sock,TRUE);
# PUTTING ALL(CURRENT/UPCOMING) SOCKET CONNECTIONS IN ARRAY
$clients = array();
# INFINTE LOOP THOURGH ALL SOCKECT CURRENT/UPCOMING CONNECTIONS, SEND/RECEIVE DATA
while (true) {
# ALL ACTIVE SOCKET CONNECTIONS
$read = $clients;
foreach($received_header as $key => $header){
if($header['time']+1 < microtime(true)){
$headers = [];
// echo $header['data'];
$lines = preg_split("/\r\n/", $header['data']);
foreach($lines as $line) {
$line = chop($line);
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)){
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
// create handshake header
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake" .PHP_EOL.
"Upgrade: websocket" .PHP_EOL.
"Connection: Upgrade" .PHP_EOL.
"Accept: text/html" .PHP_EOL.
"Sec-WebSocket-Accept: $secAccept".PHP_EOL.PHP_EOL;
// send handshake packet
if(!fwrite($clients[$key], $upgrade)){
file_put_contents('handshakes-est.txt', ($upgrade));
}
$handshakes[$key] = true;
$unset_key = $key;
}
}
/* i have rest of the script here which do further communication after connection is setup */
}
Below are some screen-shots to better understand connection behavior issue on Firefox.
Headers during handshake:
Connection behavior over websocket:
I've coded a non-evil, non-spammy IRC bot in PHP, using fsockopen and related functions. It works. However, the problem is that I need to support proxies (preferably SOCKS5, but HTTP is also OK if that is somehow easier, which I doubt). This is not supported by fsockopen.
I've gone through all search results for "PHP fsockopen proxy" and related queries. I know of all the things that don't work, so please don't link to one of them.
The PHP manual page for fsockopen mentions the function stream_socket_client() as
similar but provides a richer set of options, including non-blocking connection and the ability to provide a stream context.
This sounded promising at first, supposedly allowing me to just replace the fsockopen call with stream_socket_client and specify a proxy, maybe via a "stream context"... but it doesn't. Or does it? I'm very confused by the manual.
Please note that it must be a PHP code solution; I cannot pay for "Proxifier" or use any other external software to "wrap around" this.
All the things I've tried seem to always result in me getting a bunch of empty output from the server, and then the socket is forcefully closed. Note that the proxy I'm trying with works when I use HexChat (a normal IRC client), with the same network, so it's not the proxies themselves that are at fault.
As far as I know there is no default option to set a SOCKS or HTTP proxy for fsockopen or stream_socket_client (we could create a context and set a proxy in HTTP options, but that doesn't apply to stream_socket_client). However we can establish a connection manually.
Connecting to HTTP proxies is quite simple:
The client connects to the proxy server and submits a CONNECT request.
The server responds 200 if the request is accepted.
The server then proxies all requests between the client and destination host.
<!- -!>
function connect_to_http_proxy($host, $port, $destination) {
$fp = fsockopen($host, $port, $errno, $errstr);
if ($errno == 0) {
$connect = "CONNECT $destination HTTP/1.1\r\n\r\n";
fwrite($fp, $connect);
$rsp = fread($fp, 1024);
if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
return $fp;
}
echo "Request denied, $rsp\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
This function returns a file pointer resource if the connection is successful, else FALSE. We can use that resource to communicate with the destination host.
$proxy = "138.204.48.233";
$port = 8080;
$destination = "api.ipify.org:80";
$fp = connect_to_http_proxy($proxy, $port, $destination);
if ($fp) {
fwrite($fp, "GET /?format=json HTTP/1.1\r\nHost: $destination\r\n\r\n");
echo fread($fp, 1024);
fclose($fp);
}
The communication protocol for SOCKS5 proxies is a little more complex:
The client connects to the proxy server and sends (at least) three bytes: The first byte is the SOCKS version, the second is the number of authentication methods, the next byte(s) is the authentication method(s).
The server responds with two bytes, the SOCKS version and the selected authentication method.
The client requests a connection to the destination host. The request contains the SOCKS version, followed by the command (CONNECT in this case), followed by a null byte. The fourth byte specifies the address type, and is followed by the address and port.
The server finally sends ten bytes (or seven or twenty-two, depending on the destination address type). The second byte contains the status and it should be zero, if the request is successful.
The server proxies all requests.
<!- -!>
More details: SOCKS Protocol Version 5.
function connect_to_socks5_proxy($host, $port, $destination) {
$fp = fsockopen($host, $port, $errno, $errstr);
if ($errno == 0) {
fwrite($fp, "\05\01\00");
$rsp = fread($fp, 2);
if ($rsp === "\05\00" ) {
list($host, $port) = explode(":", $destination);
$host = gethostbyname($host); //not required if $host is an IP
$req = "\05\01\00\01" . inet_pton($host) . pack("n", $port);
fwrite($fp, $req);
$rsp = fread($fp, 10);
if ($rsp[1] === "\00") {
return $fp;
}
echo "Request denied, status: " . ord($rsp[1]) . "\n";
return false;
}
echo "Request denied\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
This function works the same way as connect_to_http_proxy. Although both functions are tested, it would be best to use a library; the code is provided mostly for educational purposes.
SSL support and authentication.
We can't create an SSL connection with fsockopen using the ssl:// or tls:// protocol, because that would attempt to create an SSL connection with the proxy server, not the destination host. But it is possible to enable SSL with stream_socket_enable_crypto and create a secure communication channel with the destination, after the connenection with the proxy server has been established. This requires to disable peer verification, which can be done with stream_socket_client using a custom context. Note that disabling peer verification may be a security issue.
For HTTP proxies we can add authentication with the Proxy-Authenticate header. The value of this header is the authentication type, followed by the username and password, base64 encoded (Basic Authentication).
For SOCKS5 proxies the authentication process is - again - more complex. It seems we have to change the authentication code fron 0x00 (NO AUTHENTICATION REQUIRED) to 0x02 (USERNAME/PASSWORD authentication). It is not clear to me how to create a request with the authentication values, so I can not provide an example.
function connect_to_http_proxy($host, $port, $destination, $creds=null) {
$context = stream_context_create(
['ssl'=> ['verify_peer'=> false, 'verify_peer_name'=> false]]
);
$soc = stream_socket_client(
"tcp://$host:$port", $errno, $errstr, 20,
STREAM_CLIENT_CONNECT, $context
);
if ($errno == 0) {
$auth = $creds ? "Proxy-Authorization: Basic ".base64_encode($creds)."\r\n": "";
$connect = "CONNECT $destination HTTP/1.1\r\n$auth\r\n";
fwrite($soc, $connect);
$rsp = fread($soc, 1024);
if (preg_match('/^HTTP\/\d\.\d 200/', $rsp) == 1) {
return $soc;
}
echo "Request denied, $rsp\n";
return false;
}
echo "Connection failed, $errno, $errstr\n";
return false;
}
$host = "proxy IP";
$port = "proxy port";
$destination = "chat.freenode.net:6697";
$credentials = "user:pass";
$soc = connect_to_http_proxy($host, $port, $destination, $credentials);
if ($soc) {
stream_socket_enable_crypto($soc, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
fwrite($soc,"USER test\nNICK test\n");
echo fread($soc, 1024);
fclose($soc);
}
I'd like to read a remote text file (ideally using fopen) using PHP. My script works using fopen when I'm using this function on a local file.
I've tried:
$file = fopen ("http://abc.abc.abc", "r");
if (!$file) {
echo "<p>Unable to open remote file.\n";
exit;
}
and I got:
Warning: fopen(http://abc.abc.abc): failed to open stream: No connection could be made because the target machine actively refused it. in C:\xampp\htdocs\NMR\nmrTest5.php on line 2 Unable to open remote file.
I've read that phpseclib could be a good option and since I can access to my files using WinSCP (SFTP) or by using Puttyfor I tried this (after copying all the files from phpseclib to my directory) hoping that I could copy locally the file and then read it with fopen (not the best thing for met but I could live with that):
include('Net/SFTP.php');
$sftp = new Net_SFTP('abc.abc.abc');
if (!$sftp->login('username', 'pass')) {
exit('Login Failed');
}
and I got:
Notice: No compatible server to client encryption algorithms found in C:\xampp\htdocs\NMR\Net\SSH2.php on line 1561
Login Failed
Interstingly, I got a different message if I was connected to the server (using WinSCP):
Notice: Error reading from socket in C:\xampp\htdocs\NMR\Net\SSH2.php on line 3362
Notice: Connection closed by server in C:\xampp\htdocs\NMR\Net\SSH2.php on line 1471
Login Failed
Any idea on how I could get it to work? Ideally I would use fopen but I'm open to other solution.
I've just been working through this exact problem myself and couldn't find any good documentation in any one single place for how to accomplish this.
I have just made a logging service that uses Monolog and basically makes a custom stream handler based on the log files that are being written to/created. As such it requires a resource (such as one created by fopen) in order to write the log files to an SFTP server.
I had it working using the ssh2 library like this:
$connection = ssh2_connect($this->host, 22);
ssh2_auth_password($connection, $this->user, $this->password);
$sftp = ssh2_sftp($connection);
//some stuff to do with whether the file already exists or not
$fh=fopen("ssh2.sftp://$sftp".ssh2_sftp_realpath($sftp,".")."/$this->logName/$this->fileName", 'a+');
return new StreamHandler($fh);
Everything was working beautifully until I went to integrate the service into a different project and realised this was only working on my development machine because it has the libssh2 library installed as outlined in this question.
Unfortunately, the production server is not so easy to add libraries to. I therefore found myself looking for a different solution.
I have used phpseclib in other projects but only for basic get(), put() and some nlist() calls.
In order to get this working I had to use a Stream object. Not very well documented but there is a good discussion here.
Based on the info there, plus some digging around in the SFTP class, particularly the get() function, this is how I managed to achieve the same functionality using phpseclib
SFTP\Stream::register();
$sftpFileSystem = new SFTP($this->host);
if (!$sftpFileSystem->login($this->user, $this->password)) {
throw new Exception("Error logging in to central logging system. Please check the local logs and email for details", 1);
}
$context = [
'sftp' => [
'sftp' => $sftpFileSystem
],
];
//some stuff to do with whether the file already exists or not
$remote_file = $sftpFileSystem->realpath('test.txt');
$sftpStream = fopen("sftp://.{$remote_file}", 'a+', null, stream_context_create($context));
if (!$sftpStream) {
exit(1);
}
return new StreamHandler($sftpStream);
note the dot (.) after the sftp:// in the call to fopen(). It wasted me a good half an hour!
This is how I ended fixing my problem usng phpseclib as suggested by #neubert in the comments of my question.
I first added the phpseclib folder on my server. Then I used this code in my PHP file to get access to my file on a remote server:
//needed for phpseclib
set_include_path(get_include_path() . PATH_SEPARATOR . 'phpseclib');
include_once('Net/SFTP.php');
//connection to the server
$sftp = new Net_SFTP('abc.abc.abc');
if (!$sftp->login('my_login', 'my_password')) {
exit('Login Failed');
}
//dump entire file in a string, convert to an array and check number of lines
else {
$text = $sftp->get('full_path_to_my_file');
}
$myArray = explode("\n", $text);
$nbline = count($myArray);
I have faced similiar issues with fopen.
Curl is useful for these purposes.
Please check with the following basic example function(if the url is https, please uncomment the CURLOPT_SSL_VERIFYPEER = FALSE line).
$url = '***THE URL***';
$result = get_web_page_by_curl($url);
if ($result['errno'] != 0) echo 'error: bad url, timeout, redirect loop ...';
if ($result['http_code'] != 200) echo 'error: no page, no permissions, no service ...';
else {
$page = $result['content'];
echo $page;
}
function get_web_page_by_curl($url) {
$agent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4) Gecko/20030624 Netscape/7.1 (ax)";
$options = array(
CURLOPT_RETURNTRANSFER => true, // return web page
CURLOPT_HEADER => false, // don't return headers
CURLOPT_FOLLOWLOCATION => true, // follow redirects
CURLOPT_ENCODING => "", // handle all encodings
CURLOPT_USERAGENT => $agent, // who am i
CURLOPT_AUTOREFERER => true, // set referer on redirect
CURLOPT_CONNECTTIMEOUT => 120, // timeout on connect
CURLOPT_TIMEOUT => 120, // timeout on response
CURLOPT_MAXREDIRS => 10, // stop after 10 redirects
//CURLOPT_SSL_VERIFYPEER => FALSE // this line makes it work under https
);
$ch = curl_init($url);
curl_setopt_array($ch, $options);
$content = curl_exec($ch);
$err = curl_errno($ch);
$errmsg = curl_error($ch);
$header = curl_getinfo($ch);
curl_close($ch);
$header['errno'] = $err;
$header['errmsg'] = $errmsg;
$header['content'] = $content;
return $header;
}
I need help implementing push notifications in swift. I followed the ray wenderlich tutorial http://www.raywenderlich.com/32960/apple-push-notification-services-in-ios-6-tutorial-part-1 but it does not mention how you actually call or run the php push notification script in Xcode. This is how I am attempting to call the script right now:
let request = NSMutableURLRequest(URL: NSURL(string: "http://website.com/pushNotification")!)
request.HTTPMethod = "POST"
let dataDictionary:[String:String] = ["NotificationData":"\(deviceTokenString)<*&*>password<*&*>my first push notification"]
let data:NSData = try! NSJSONSerialization.dataWithJSONObject(dataDictionary, options: [])
request.HTTPBody = data
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// Create a NSURLSession task with completion handler
let task:NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
// Convert the data into a dictionary
let response:[String:String] = (try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)) as! [String:String]
// Check if there was an error
if let result = response["result"] {
if result == "success"{
NSLog("Message deleviered successfully")
}
else if result == "error"{
NSLog("Message could not be deleviered")
}
}
})
// Run the task
task.resume()
Heres the php script it hits:
<?php
// Get the data from the request
$json = file_get_contents('php://input');
$data = json_decode($json, true);
$pushData = $data['NotificationData'];
// Format data
$keywords = explode("<*&*>", $pushData);
// Assign data into variables
$deviceToken = $keywords[0];
$passphrase = $keywords[1];
$message = $keywords[2];
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'signingcertificate.p12');
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.sandbox.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if (!$fp) {
//exit("Failed to connect: $err $errstr" . PHP_EOL);
}
// Create the payload body
$body['aps'] = array(
'alert' => $message,
'sound' => 'default'
);
// Encode the payload as JSON
$payload = json_encode($body);
// Build the binary notification
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));
if (!$result) {
echo '{"result" : "error"}';
}
else {
echo '{"result" : "success"}';
}
// Close the connection to the server
fclose($fp);
?>
But then Xcode gives me this error:
fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}: file /Library/Caches/com.apple.xbs/Sources/swiftlang_PONDEROSA/swiftlang_PONDEROSA-700.1.101.6/src/swift/stdlib/public/core/ErrorType.swift, line 50
Im almost positive the error has something to do with the way I call for the php script but I don't know how this type of php script should be called. Please help! Any suggestions or insight you have will be appreciated!
The link does tell you what to do with the PHP script. You aren't suppose to call that through Xcode.
As the tutorial states:
As I’ve mentioned a few times before, you need to set up a server that sends the push notifications to your app. For this first test, you’re not going to set up a server just yet. Instead, I’ll give you a very simple PHP script that sets up a connection to APNS and sends a push notification to a device token that you specify. You can run this straight from your Mac.
...
You should copy the device token from the app into the $deviceToken variable. Be sure to leave out the spaces and brackets; it should just be 64 hexadecimal characters. Put your private key’s passphrase into $passphrase, and the text you wish to send in $message.
Copy your ck.pem file into the SimplePush folder. Remember, the ck.pem file contains both your certificate and the private key.
Then open a Terminal and type: .....
The PHP is a simple example of what your server would do. You need to build the server as well that will invoke a call to apple's APNS when a certain event occurs. The mobile app itself doesn't invoke a push notification.
I need to connect to Apple PNS via proxy. I am using PHP to develop the application. Currently, I have googled a lot but nowhere I am able to find an example of using Proxy with stream context.
Also did the steps mentioned here:
Using Charles and SSL Apple push address
Below is the code snippet. Anyone please suggest, its highly urgent
if(PROXY_ADDRESS != '' && PROXY_PORT !='')
{
$opts = array('http' => array('proxy' => 'tcp://'.PROXY_ADDRESS.':'.PROXY_PORT, 'request_fulluri' => true));
$streamContext = stream_context_create($opts);
}
else
{
$streamContext = stream_context_create();
}
stream_context_set_option($streamContext, 'ssl', 'local_cert', $this->_privateKey);
if($this->_privateKeyPassphrase !== '')
stream_context_set_option($streamContext, 'ssl', 'passphrase', $this->_privateKeyPassphrase);
$fp = fopen('https://gateway.sandbox.push.apple.com:2195', 'r', false, $streamContext);
I am not getting any error/exception, but the push message is not received.
To those who may encounter the same problem:
Apple PNS doesn't allow connections coming via Proxy Server. It only accepts direct connections. My problem got resolved when I made a direct connection instead of going via a proxy.