Only from PHP: Unable to connect APNS gateway.push.apple.com:2195
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).
gorodezkiy
Updated on June 07, 2022Comments
-
gorodezkiy almost 2 years
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, includeentrust_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)
andVerify 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.