'Unable to set private key file' attempting to send push using php - php

I'm using the Ray Wenderlich push tutorial as a reference to set up push for my app, something I have done dozens of times before, literally dozens, and its always gone smoothly, until now.
When executing the php file to manually test sending the push I'm getting the error:
'Unable to set private key file ... ck.pem'
at the last line:
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem');
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);
ck.pem is a concatentation of the ssl certificate and private key and I CAN use these sussesfully with the following command:
openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushCert.pem -key PushKey.pem
When I execute this I get a big spiel and output regarding SSK handshake reading/writing N bytes which indicates it was sucessful and thus the certificate and key must be valid.
So I don't understand why there is no problem using the certificate and key separately when used as arguments to the openssl command line above but there is when used in the concatenated from in the php file.
After getting the problem initially I deleted everything - I clearned out all the certificates and keys from the keychain, deleted all the provisioning profiles etc. The whole lot and started with a clean slate again to make sure I didn't make a mistake somewhere. Same results - keep getting the 'Unable to set private key file'.
I saw a past posting where somebody had the same problem and they solved it by executing the php file using sudo, but that didn't work for me.
Any suggestions, this is driving me mad, especially as I've done it before dozens of times previously sucessfully.

I had this once, after many hours of frustration I tracked the problem down to the fact that I was using TextEdit to edit the .php file's contents and TextEdit was inserting invisible characters.
Not at the end of the line, actually in the line itself i.e. if the original text in the Ray Wenderlich file is like this:
// Put your private key's passphrase here:
$passphrase = ‘cpushchat';
Then after using TextEdit to change the password it looked like this when you viewed it:
// Put your private key's passphrase here:
$passphrase = ‘mypassword';
But if you viewed it in a hex edited, what TextEdit has actually done was to insert invisible (invisible in the in the text viewer) characters like this:
// Put your private key's passphrase here:
$passphrase = ‘E28098mypassword';
Which resulted in the password being incorrect of course and thus leading to that message.

Related

Only from PHP: Unable to connect APNS gateway.push.apple.com:2195

It's a late night. I just spent 10 hours in google/stackoverflow search and experiments. And seems I hate Apple Push Notifications. I'm totally frustrated and will appreciate any help.
Thank you.
The problem:
The PHP code for sending Apple Push Notifications, which successfully worked two weeks ago stopped to work now and throws following errors:
PHP Warning: stream_socket_client(): Failed to enable crypto in /home/...
PHP Warning: stream_socket_client(): unable to connect to ssl://gateway.push.apple.com:2195 (Unknown error) in /home/...
It stopped to work on two separate servers, which are using separate scripts for APNs sending.
Environment:
Servers: CentOS 6.5 with PHP 5.4.32 and Ubuntu 14.04.3 with PHP 5.5.9
APN: In production mode
Certificates: tested with 700+ push notifications.
One of the servers is using https://github.com/immobiliare/ApnsPHP, other - https://github.com/antongorodezkiy/wp-apn, on localhost I tested simple file without using any third party code.
Investigation:
For all cases below I used the same active device token and the same production PEM certificate.
php
However, even this simple code doesn't work on both servers and localhost and return the same error as above:
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', '/absolute/path/to/apn_prod.pem');
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
I also tried to play with stream_context_set_option() options, include entrust_2048_ca.cer, etc, and even some options from this article. Although provided code worked without any modifications before August 2015.
openssl
Connection worked with openssl (link):
openssl s_client -connect gateway.push.apple.com:2195 -cert /absolute/path/to/apn_prod.pem -debug -showcerts -CAfile /absolute/path/to/server-ca-cert.pem
And got with CONNECTED(00000003) and Verify return code: 0 ( ok ).
telnet
Connection worked with telnet:
-sh-4.1$ telnet gateway.push.apple.com 2195
Trying 17.172.233.150...
Connected to gateway.push.apple.com.
pecl apn
It didn't send push notification. I just tried to use adaptation of sample code, but got the error Invalid token. The token is active and same token I used everywhere and for Houston and Ruby too.
houston
It worked with Houston
apn push "0346a53f...231d9d6abe11" -c /absolute/path/to/apn_prod.pem -m "Hello from the command line!" -e "production"
ruby
I'm not a Ruby programmer (yet at least), but after success with Houston, I found and adapted Ruby code without Houston dependency.
And it worked:
#!/usr/bin/env ruby
require 'openssl'
require 'socket'
require 'json'
token = "0346a53f...231d9d6abe11"
cert = File.read("/absolute/path/to/apn_prod.pem")
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new(cert, '') #set passphrase here, if any
ctx.cert = OpenSSL::X509::Certificate.new(cert)
sock = TCPSocket.new('gateway.push.apple.com', 2195) #development gateway
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl.connect
payload = {"aps" => {"alert" => "Oh hai!", "badge" => 1, "sound" => 'default'}}
json = payload.to_json()
token = [token.delete(' ')].pack('H*') #something like 2c0cad 01d1465 346786a9 3a07613f2 b03f0b94b6 8dde3993 d9017224 ad068d36
apnsMessage = "\0\0 #{token}\0#{json.length.chr}#{json}"
ssl.write(apnsMessage)
ssl.close
sock.close
puts "End"
Questions:
What's wrong with PHP? Is there some bug related to this issue? (I didn't find bug report though)
Any ideas how to solve this issue?
Any ideas what could be the difference in PHP and Ruby cases (I assume that Python or Perl could work fine too)? I even tried to read PHP sources, but without success to understand how stream_socket_client() implemented.
Please help.
I've found the issue and fixed it.
The problem was in .pem certificate. Somehow there were two certificates in one file for both production and development .pem files. The same .pem file with two certificates was in the repo for a long time but APNs stopped to work only few months ago. Maybe something was upgraded/changed on the Apple's side.
I assume the Ruby code somehow removes certificate duplication or maybe it took only first certificate, so it worked in Ruby.
However, the solution was to remove the second certificate from the .pem file. After that APNs started to work and they work now (I received some just yesterday).
If you're just reusing an old Certificate Signing Request (CSR), make sure to remove the expired/old APNs certificate from your Keychain before exporting the new one and its private key as p12 file. If you don't do that, the PEM file you generated out from the exported p12 will still contain the expired/old certificate which doesn't goes well with Apple's Push provider. Thus resulting to unable to connect to ssl....

How to set trusted certificate authorities list to socket client in PHP?

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
]]);

Messing up the Apple Push Notification Service

I have been developing an app that uses push notifications. I have been messing around ALOT trying to fix the stupid provisioning stuff, and I really don't know what's what anymore.
I managed to fix the whole development push notifications. And now, we are close to release. However, the switch to Production notification didn't go as smooth as expected. And I still don't really understand the whole provisioning-thing.
I thought I just had to switch out the .pem file with a new one, generated from a new .cer and .p12 file, aswell as remove the "sandbox" from the url it's trying to connect to. This just resulted in "Failed to connect: 0" in my .php sending the push.
.php
$ctx = stream_context_create();
stream_context_set_option($ctx, 'ssl', 'local_cert', 'dp.pem')
stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195', $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
if(!$fp)
exit("Failed to connect: $err $errstr" . PHP_EOL);
After messing around with the .p12 and .cer and .cert and .pem and .mobileprovision .ipa, I really don't know what to do anymore.. Why do we have to convert to .pem and merge them? Is this REALLY the way Apple intended for us to do this? Or have I been following a pretty redicolus tutorial?
What .cer file do I need? Which profile should the app use? AppID vs provision vs device vs Certificate. What the hell.. Should I have to do anything other than switching to a new .pem file and remove the "sandbox". I have activated and downloaded the "Production Push SSL Certificate" from the portal, and generated my .pem file from that. I have tried reading up on this, but it is reeeally hard and a stupid setup! I'm sure I have done something wrong. But can't figure out what, where and why!
Push notification services - great tutorial, you will find php script + answer for all your questions.

How to make certificate in OpenSSL to work with php?

Recently i decided to add banklink payment option for my php store and after reading specifications of implementation everything seems okey, but the 1 point of it. All public key(certificates) exchange are in X509 format. Now what does that last one mean and how it's different from regular password protected .pem file?
Also with regular password protected .pem file i cannot use php function like openssl_verify() signed by openssl_sign() function.
Could i get some advice here please since the bank that offering this payment method has very little information on this and im totally newb to this.
So the routine i need to do here is generate request.pem for them and send it to them. After that they will sign it or whatever i dunno and i should be able to use it in my application.
Please, tell me if my information is not enough because as i told i don't know much when in comes to certificates or openssl.
PEM file contains encrypted and base64-encoded 'raw' certificate/private key value, so functions that work with PEM should also work with raw certificates. OpenSSL should be able to convert from one format to another.
You have to use curl concept.
curl_setopt($ch, CURLOPT_SSLCERT,'CA.pem');
Honestly, the OpenSSL capabilities in PHP are abysmal. Your best bet is to go for an awesome package like phpseclib.
long time ago i was doing something similar. The difference is they just gave me the pem file, and i used it to connect to their server.
Below I wrote my guiessing for you. :)
Generate myreq.pem using openssl tool.
Send myreq.pem to them and get signed certificate ca.pem.
Use ca.pem in PHP like below.
$context = stream_context_create();
stream_context_set_option($context, 'ssl', 'verify_peer', false); //or can be true
stream_context_set_option($context, 'ssl', 'cafile', "ca.pem");
stream_context_set_option($context, 'ssl', 'local_cert', "ca.pem");
stream_context_set_option($context, 'ssl', 'passphrase', 'your pem file password');
//instead tls can be ssl, sslv3, sslv2 depending requirements, 10 is timeout
$sock = stream_socket_client("tls://host:port", $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $context);
fwrite($sock, "Hi server\r\n");
$strResponse;
while(!feof($sock)) { $strResponse = $strResponse . fgets($sock, 1024); }
fclose($s);
echo $strResponse;

How to know whether .pem file is correct or not

I have created a .pem file to be used for Production push notification,
and uploaded in server.
But when sending push notification, it is giving error like this.
$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT, $ctx); not working, return failed.
what is this error, how to solve it.
Finally, how to know whether the .pem file is correct or not
You could use "certtool" from the GnuTLS binaries to validate the certificate (certtool -i --infile cert.pem). Windows binaries are also available.
I don't have an answer for your stream_socket_client problem. Make sure the context is initialized correctly, an example is shown in the documentation.

Categories