I am currently using a tcp socket to exchange data between a server and a client using socket streams in PHP. I want each client to have a unique certificate (which I will generate for them) to access my server so that only people I know will be able to use it.
I have searched and found only how to use certificate on the server side, which will enable me to make a SSL socket connection, but what I actually need is to use a client server certificate to identify the client that is connecting to my server. Is this possible using PHP ?
I'd imagine the client to connect like this:
$clientCert = './clientCert.pem';
$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $clientCert);
$server = stream_socket_client('ssl://IP', $error, $errorString, 2, STREAM_CLIENT_CONNECT, $streamContext);
But how does the server handle (identify, etc) this client side certificate ?
Hope I was clear with my question.
Thanks in advance,
Rafael
You should use stream_context_set_option to set verify_peer to true; this will require the other side to verify that the client's certificate is trusted. The verification is done by checking that the certificate is signed by a trusted authority, so you need to specify where the authority's public key can be found through either the cafile or capath option.
Note that the above holds true both on the server side (to allow you to authenticate your clients) and on the client side -- the server is also not authenticated unless you explicitly make it so.
Related
I'm running the next script from my local host and the production server, and Im getting different outputs. Anyone knows why am I getting that false from my localhost?
<?php
$host = 'ssl://mail.companyname.org';
$port = 993;
$error = 0;
$errorString = "";
var_dump(fsockopen($host, $port, $error, $errorString, 30));
var_dump($errorString);
var_dump($error);
Local host output:
bool(false)
Production server output:
resource(4) of type (stream)
UPDATE: after the comments/answer I have modified the code and now Im getting this output on my local host:
PHP Warning: fsockopen(): SSL operation failed with code 1. OpenSSL
Error messages: error:1416F086:SSL
routines:tls_process_server_certificate:certificate verify failed in
/tmp/test.php on line 7 PHP Warning: fsockopen(): Failed to enable
crypto in /tmp/test.php on line 7 PHP Warning: fsockopen(): unable to
connect to ssl://mail.twmdata.org:993 (Unknown error) in /tmp/test.php
on line 7 bool(false) string(0) "" int(0)
it seems this is problem with server certificate :
first you can check if your server certificate and its chains are valid by this:
https://www.sslshopper.com/ssl-checker.htm
if somethings were wrong in ssl-checker?
you can try to correct SSL certificate configs in companyname.org
if you succeed and error was persists ?
you have to add Certificate files manually.
if you have a self-signed certificate:
you have to add Certificate files manually.
if you dont have certificate nor you dont care about man-in-the-middle attack,
you can still use SSL without Certificate.
turn off php fsock Certificate check (not recommended)
its recommended to have a certificate at least a self-signed. if you have a self-signed try 1 solution.
I have found the Problem
You have exposed your Domain name in your PHP Warning Log, so i have checked your domain SSL.
after i check your company`s domain certificate using this tool:
https://www.sslshopper.com/ssl-checker.html#hostname=twmdata.org
it had 2 errors with your certificates:
This certificate has expired (0 days ago). Renew now.
None of the common names in the certificate match the name that was entered (twmdata.org). You may receive an error when accessing this site in a web browser.
so it seems you have to renew your certificate first
Update:
i have found this answer maybe helpful
https://stackoverflow.com/a/40962061/9287628
it suggested to use
stream_context_create(['ssl' => [
'ciphers' => 'RC4-MD5'
]])
as #ChrisHaas suggested connecting with stream_context_create and stream_socket_client brings you a lot of option if you want to dictate the cert directory or you want to turn off certificate check.
Per the documentation for fsockopen
The function stream_socket_client() is similar but provides a richer set of options, including non-blocking connection and the ability to provide a stream context.
Basically, fsockopen is very low-level but without many options, or, arguably, "sane defaults".
Instead, you can switch to stream_socket_client which will allow you to specify a context as the last parameter, and that object has many options, including a dedicated one with over a dozen options specific to SSL. The object created from this function is compatible with fwrite and other functions, so it should do everything you are hoping for.
$context = stream_context_create([/*Options here*/]);
$connection = stream_socket_client($host, $errno, $errorString, 30, null, $context);
Now, what options should you use?
The worst option that might work is probably verify_peer. I say "worst" because you are throwing away the verifiability part of SSL/TLS and only using it for encryption, and doing this will make you susceptible to MitM attacks. However, there's a place and time for this, so you could try it if the other options are too complicated.
$context = stream_context_create(['ssl' => ['verify_peer' => false]]);
$connection = stream_socket_client($host, $errno, $errorString, 30, null, $context);
Instead, I'd recommend using either cafile or capath which do the same thing except the former is for a file while the latter is for a directory.
$context = stream_context_create(['ssl' => ['verify_peer' => true, 'cafile' => '/path/to/file']]);
$connection = stream_socket_client($host, $errno, $errorString, 30, null, $context);
What certs should you use? We use this library to pull in recent CA files on a periodic basis, very convenient. There's a little bit of setup that's per-project but once you get it it goes pretty fast. See this for pulling in a CA file at a well-known location.
One other last option is local_cert which you can use with a PEM file that holds the certificate and private key from the server, if you have access to that.
EDIT
The cert on mail.twmdata.org:993 is different than the web server's cert that other people are talking about, which is generally a best practice. You can inspect that cert using:
openssl s_client -connect mail.twmdata.org:993 -servername mail.twmdata.org
If you do that, you'll see that the server has a self-signed cert which you can get around by setting the verify_peer option to false.
Remove the # symbol. You are hiding error messages that might tell you what the problem is. You should also set a variable in the errorno argument to fsockopen() and echo it for debugging.
My guess would be that you haven't installed PHP with SSL support on your local server. See here.
Companyname.org might also block requests from your local server that are allowed from the production server.
I'm developing a php client to interact with Nominet EPP server, and as I found in their web site:http://registrars.nominet.uk/namespace/uk/registration-and-domain-management/registrar-systems/epp/registration
I need to use a "Verisign Class 3 Public Primary Certification Authority" root certificate. I found one in here (Verisign is bought by Symantec): https://knowledge.symantec.com/support/mpki-for-ssl-support/index?page=content&id=SO5624&actp=LIST&viewlocale=en_US
When I use it in my php code:
//$context = stream_context_create(array($this->protocol => $options));
$context = stream_context_create();
stream_context_set_option($context, $this->protocol, 'local_cert', __DIR__ . '/../../certificates/'.$this->certificate_path);
$errno = false;
$errstr = false;
$this->socket = stream_socket_client($this->protocol.'://'.$this->hostname.':'.$this->port, $errno, $errstr, 5 , STREAM_CLIENT_CONNECT, $context);
Everything is ok: path of certificate, port, hostname ...
And I get:
"stream_socket_client(): Unable to set private key file ..."
I know I need a private key, but in Symentec website they don't give a private key.
Do anyone have any idea ?
Thanks so much,
Hassan,
You are not using the right context option. This option is to define your certificate. What you want is use the certificate you found to authenticate the peer server, to make sure you are really talking to the right one.
As stated by this:
In order to verify the identity of the secure server you will need the
'Verisign Class 3 Public Primary Certification Authority' root
certificate available free from www.verisign.com (the certificate is
also distributed with most web browsers).
On PHP Doc:
local_cert
string Path to local certificate file on filesystem. It
must be a PEM encoded file which contains your certificate and private
key.
But what you want is this:
cafile
Location of Certificate Authority file on local filesystem which
should be used with the verify_peer context option to authenticate the
identity of the remote peer.
I'm busy with a curl php library which needs to connect to an FTPS server.
I have this semi working... If I connect to ftp://domain.com then it does work. If I watch the comms on the server with tcpflow I see it logging in with AUTH TLS and and all the comms is encrypted. The file is uploaded so all's good..
What I'm unsure of is if its valid to try connecting instead to ftps://domain.com?
The reason I'm asking is because if I change the protocol from ftp to ftps in curl then the login fails and the server (watching tcpflow comms) says that the login has failed:
191.101.002.204.00021-088.099.012.154.51630: 530 Please login with USER and PASS.
Also, when I watch the comms when trying to connect to ftps:// I don't see the client issuing the AUTH TLS command as it does with plain ftp://
The problem I have is that it seems that my client's FTP server we have to ultimately connect to doesn't seem to allow connections without the ftps:// protocol.
If I connect using lftp I can do so using ftps:// but then I have to disable ssl:
set ftp://ssl-allow no
If I try the lftp connection using ftp:// it just hangs on the login command...
I'm not really that experienced with FTP or TLS / SSL so I don't know if its maybe because the client's server doesn't have the certificates set up correctly..
Here is a portion of my curl code which works with ftp:// but not ftps://
// Works
$url = "ftp://proxy.plettretreat.co.za/";
// Does not work
$url = "ftps://proxy.plettretreat.co.za/";
$port = 990;
$username = "ftpuser";
$password = "pass";
$filename = "/test.php";
$file = dirname(__FILE__)."/test.php";
$c = curl_init();
// check for successful connection
if ( ! $c)
throw new Exception( 'Could not initialize cURL.' );
$options = array(
CURLOPT_USERPWD => $username.':'.$password,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_BINARYTRANSFER => 1,
CURLOPT_FTP_SSL => CURLFTPSSL_ALL, // require SSL For both control and data connections
CURLOPT_FTPSSLAUTH => CURLFTPAUTH_TLS, // let cURL choose the FTP authentication method (either SSL or TLS)
CURLOPT_UPLOAD => true,
CURLOPT_PORT => $port,
CURLOPT_TIMEOUT => 30,
);
Another thing I'm unsure of is that my client has given me an IP address to connect to.. Can an IP address be used in ftps? I would have thought that certificates are mostly certifying a domain name?
tl;dr
1) Can I use ftps://domain.com to connect using CURL PHP?
2) If I can use ftps:// in curl, then how do I get curl to log in (issue auth tls command)?
3) Can an FTP server use SSL / TLS with only an IP address?
Thanks...
John
Many many hours of struggling led me to an eventual answer.
Part of the answer was that the client server and the FTP server had "overly" strict firewall rules blocking the passive ports.
I was getting the following error:
Error no: 35; Error: SSL connect error.
Error 35 was because of the firewall rules. Once those were relaxed that error went away, but as a note, you will also see this error if the client machine is NAT'ed. If it is you need to set the curl option:
curl_setopt($c, CURLOPT_FTPPORT, '1.2.4.5' ); // change to your actual IP.
This tells the FTP server where to open up its data channel (instead of trying to open it to the client server's internal address).
Anyway, once the firewall and FTPPORT options were set I got:
Error no: 30; Error: bind(port=0) failed: Cannot assign requested address
This one baffled me for quite a while as everything looked correct.
I eventually stumbled upon a few thread here and elsewhere which talk about an issue with older versions of Curl using NSS for its encryption. I checked and I was using libcurl version 7.19.7 (about 8 years old) and sure enough it uses NSS...
I updated my Curl using this guide: https://www.digitalocean.com/community/questions/how-to-upgrade-curl-in-centos6.
That updated me to libcurl 7.52.1 which uses OpenSSL and lo and behold, my app started working...
So, if you're having issues connecting curl-ftp to a FTPS server, check the FTPPORT (passive IP) if you're NAT'ed, check your firewall, but most importantly, check your curl:
<?php
print print_r(curl_version());
?>
I hope this helps someone..
In the context of the IHE Connectathon, I want to make a raw socket server responsing to the ATNA profile, which requires TLS sockets with both ends certificates.
My issue if summed up in this message :
https://groups.google.com/d/msg/eu_connectathon/O-VGI_3cltw/ARsElA65ZkkJ
Edit: Sorry, the Google Groups isn't public, here is the message :
Hi Florian,
What exactly does the error message "Server asked for a certificate,
but list of issuers doesn't contain valid certificate authority."
mean and did the implementation of the TLS Tools Client change during
the last years, or am I using the wrong certificates?
The message means that the server has sent the CertificateRequest
message to the clienmt with no values in the certificate_authorities
field.
I ran into this problem last year and discussed this with a developer
of the TLS Tools. He claimed that if a server didn't include this
field, the client wouldn't have a clue what kind of certificate to
return, assuming a scenario where you would connect top multiple
affinity domains, each with their own CA.
It appears that you can instruct OpenSSL to return this value by
calling SSL_CTX_set_client_CA_list, e.g. in
DcmTLSTransportLayer::addTrustedCertificateFile. I haven't tested this
with the TLS tools yet, but I hope to accomplish that before the
connectathon starts.
But my implementation, in PHP, is not the same as their's.
It looks like PHP is missing the "SSL CTX set client CA list" possibility, to tell the client which certificate authority it should use.
$context = stream_context_create();
if ($certificate) {
// Server certificate + private key
stream_context_set_option($context, 'ssl', 'local_cert', "/path/to/server.pem");
stream_context_set_option($context, 'ssl', 'passphrase', $passphrase);
// Client public certificates
stream_context_set_option($context, 'ssl', 'cafile', "/path/to/ca.pem");
stream_context_set_option($context, 'ssl', 'allow_self_signed', false);
stream_context_set_option($context, 'ssl', 'verify_peer', true);
stream_context_set_option($context, 'ssl', 'peer_name', "TlsTools2");
stream_context_set_option($context, 'ssl', 'capture_peer_cert', true);
stream_context_set_option($context, 'ssl', 'capture_peer_cert_chain', true);
}
$this->__socket = #stream_socket_server("tcp://$address:$port", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context);
The IHE Gazelle TLS client tells me "Server asked for a certificate, but list of issuers doesn't contain valid certificate authority."
The messages between client and server pass, but the test is not OK, as "not enough secure", as tells the message.
Do you see a problem, and does PHP have more options I didn't see ?
Thanks for your help.
Edit: As #rdlowrey suggested me, I just created a bug report : https://bugs.php.net/bug.php?id=69215
As I mentioned in the initial comment:
PHP's stream server implementation has never actually used SSL_CTX_set_client_CA_list() for encrypted streams.
This has been corrected upstream as referenced in the associated bug report:
https://bugs.php.net/bug.php?id=69215
This change will be reflected once the PHP 5.6.8 binary is released (or you can build PHP manually against the current source prior to that release).
Implementation
Using an updated binary the OP's example code will work as expected without modification. A simple example of a crypto context to use in servers:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true
]]);
In the above example PHP automatically uses the names from certs found in the my-ca-certs.pem file referenced above when sending the client CA list as part of the TLS handshake.
Notes
When enabling peer verification in encrypted server streams via "verify_peer" => true PHP will not automatically enable peer name verification. Unless you only wish to allow a single certificate holder (with a specific known name) access to your server this is exactly what you want. This default behavior supports the more common use-case of allowing any client whose certificate was signed by a trusted CA to establish connections to your server. However, if you wish to enforce name verification in an encrypted server modify the above context example as shown here:
<?php
$serverCtx = stream_context_create(['ssl' => [
'local_cert' => '/path/to/my-server-cert.pem',
'passphrase' => 'elephpant',
'cafile' => '/path/to/my-ca-certs.pem',
'verify_peer' => true,
'verify_peer_name' => true, // verify the name on the cert
'peer_name' => 'zanzibar' // ensure the cert's name matches this
]]);
I am using the certificate, and the private key
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', $certfile);
stream_context_set_option($ctx, 'ssl', 'passphrase', $pass);
$fp = stream_socket_client('ssl://gateway.xyz.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx);
Its running in my local XAMPP Server, but its not working in the external server:
Warning: stream_socket_client() [function.stream-socket-client]: unable to connect to ssl://gateway.sandbox.push.apple.com:2195 (Connection timed out) in /home/biranchi/public_html/push.php on line 42
Failed to connect 110
What is the error? Do i need to change some setting in the server?
I had fixed the issue by opening the port 2195 on the production server. You can verify by following command $telnet gateway.push.apple.com 2195
-bash-3.2# telnet gateway.push.apple.com 2195
Trying 17.149.38.141...
Connected to gateway.push.apple.com (17.149.38.141).
Escape character is '^]'.
Connection closed by foreign host.
Check your personal firewall settings and make sure you're not blocking this out. Try disabling the firewall.
Also, some APIs like requests to come from an actual domain rather than a desktop. I don't have reason to believe Apple works this way, but that's something to check also.
Also make sure and ping gateway.sandbox.push.apple.com and make sure you have a good connection.
You have to set your firewall to allow all the 17.0.0.0/8 block (it all belongs to Apple!). Check THIS ANSWER
And according to Apple:
The APNs servers use load balancing, so your devices won't always connect to the same public IP address for notifications. It's best to allow access to these ports on the entire 17.0.0.0/8 address block, which is assigned to Apple.
If you are using CSF firewall (like me), I'd recommend to add this line to csf.allow file:
tcp|out|d=2195|d=17.0.0.0/8
Then restart CSF. Using the above instead of just "17.0.0.0/8" will allow only outbond connections to Apple and specifically to port 2195. NSA won't like it but this is much more precise and safe! ;)