通过GAE的iOS推送通知(APN),SSL握手失败

时间:2016-06-18 00:36:16

标签: python ios google-app-engine ssl apple-push-notifications

我试图使用此RPC处理程序从Google AppEngine应用程序实例中显示iOS推送通知的概念证明...

PAYLOAD = {'aps': {'alert':'Push!','sound':'default'}}
TOKEN = '[...]'


class APNsTest(BaseRPCHandler):

  def get(self, context, name):
    self._call_method(context, name)

  def send_push(self):

    # certificate files
    filename = 'VisitorGuidePush'
    abs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../archive/certificate'))
    ca_certs = os.path.abspath(os.path.join(abs_path, '%s.ca'%filename))
    certfile = os.path.abspath(os.path.join(abs_path, '%s.crt'%filename))
    keyfile = os.path.abspath(os.path.join(abs_path, '%s.key'%filename))

    # serialize payload
    payload = json.dumps(PAYLOAD)

    # APNS server address...
    # apns_address = ('api.development.push.apple.com', 443) # Development server
    # apns_address = ('api.development.push.apple.com', 2197) # Development server
    # apns_address = ('api.push.apple.com', 443) # Production server
    apns_address = ('api.push.apple.com', 2197) # Production server

    # a socket to connect to APNS over SSL
    _sock = socket.socket()
    _ssl = ssl.wrap_socket(_sock, keyfile=keyfile,
                                  certfile=certfile,
                                  server_side=False,
                                  cert_reqs=ssl.CERT_REQUIRED,
                                  ssl_version=ssl.PROTOCOL_TLSv1,
                                  ca_certs=ca_certs)
    _ssl.connect(apns_address)

    # Generate a notification packet
    token = binascii.unhexlify(TOKEN)
    fmt = '!cH32sH{0:d}s'.format(len(payload))
    cmd = '\x00'
    message = struct.pack(fmt, cmd, len(token), token, len(payload), payload)

    _ssl.write(message)
    _ssl.close()

    return self.response_result(PAYLOAD)

执行“_ssl.connect(apns_address)”时需要帮助解决此错误

SSLError: [Errno 1] _ssl.c:507: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure

我的PEM文件(源自.p12)和设备令牌是我们团队的移动开发人员一周前生成的,有关验证这些内容的建议会有所帮助。现在我相信目前有效。

在指定TLSv1协议时,我注意到握手失败标识了sslv3。

我尝试过wrap_socket和apns_address的许多变体和组合,并且由于握手失败而一直停止。这导致我怀疑我使用pem证书的方式有问题。

我一直用于wrap_socket的主要引用是Using OpenSSLTLS/SSL wrapper for socket objects,更不用说几个StackOverflow帖子了。

  

请提供有关适当的密钥文件,certfile和ca_certs值以及可用于基于GAE的APN通信的任何其他建议或资源的建议。谢谢〜

更新了问题......

原始.p12已使用Pusher验证,并通过openssl ...

进行划分
openssl pkcs12 -in vgp.p12 -out VisitorGuidePush.key -nodes -nocerts
openssl pkcs12 -in vgp.p12 -out VisitorGuidePush.crt -nodes -nokeys
openssl pkcs12 -in vgp.p12 -out VisitorGuidePush.ca -nodes -cacerts

我收到一个与ca_certs相关的新错误...

SSLError: [Errno 0] _ssl.c:343: error:00000000:lib(0):func(0):reason(0)

删除ca_certs要求或传入.p12或.crt等其他文件会导致返回原始握手失败。

3 个答案:

答案 0 :(得分:1)

考虑使用像pyapns这样的库,这是我用来获取推送通知以便在GAE上工作的。要测试您是否使用了正确的密钥/证书文件,可以使用Pusher之类的应用。另外,我知道要在GAE上获得SSL功能,你必须启用计费,所以这可能是问题所在。祝你好运!

答案 1 :(得分:1)

相应的支持文件以Creating a Universal Push Notification Client SSL Certificate作为 p12 文件开头。

接下来,利用命令行openssl将 p12 解析为所需的证书和密钥文件......

openssl pkcs12 -in VisitorGuide.p12 -out VisitorGuide.key -nodes -nocerts
openssl pkcs12 -in VisitorGuide.p12 -out VisitorGuide.crt -nodes -nokeys
openssl pkcs12 -in VisitorGuide.p12 -out VisitorGuide.pem -nodes

最后获取合格的证书颁发机构文件(来自Troubleshooting Push Notifications

  

除了SSL身份(证书和相关的私人身份证明)   密钥)由会员中心创建,您还应该安装Entrust CA.   (2048)您的提供商的根证书。

Entrust.net Certificate Authority (2048) download ~encoust_2048_ca.cer

请注意,每个GAE实例都在/etc/ca-certificates.crt上托管自己的证书颁发机构,如此处所述Using OpenSSL

将这些文件添加到您的项目中,您可以制作两个同样有效的ssl套接字对象中的一个......

_ssl = ssl.wrap_socket(_sock, keyfile=VisitorGuide.key,
                              certfile=VisitorGuide.crt,
                              server_side=False,
                              cert_reqs=ssl.CERT_REQUIRED,
                              ssl_version=ssl.PROTOCOL_TLSv1,
                              ca_certs=entrust_2048_ca.cer)

...或...

_ssl = ssl.wrap_socket(_sock, certfile=VisitorGuide.pem,
                              server_side=False,
                              cert_reqs=ssl.CERT_REQUIRED,
                              ssl_version=ssl.PROTOCOL_TLSv1,
                              ca_certs=entrust_2048_ca.cer)

TLS/SSL wrapper for socket objects 17.3.4.3. Combined key and certificate解释了为什么两者都是有效的参数选项。

在我提供最终代码块之前,我必须指出有关APNs地址的事情(这被证明是关键点,允许我解决握手失败并获得GAE和APN之间的SSL连接)

根据iOS开发人员库APNs Provider API

  

发送远程通知的第一步是与相应的APN服务器建立连接:

     

开发服务器:api.development.push.apple.com:443

     

生产服务器:api.push.apple.com:443

     

注意:您也可以在与APN通信时使用端口2197。例如,您可以执行此操作,以允许APN通过防火墙,但阻止其他HTTPS流量。

但直到我挖掘Pusher来源,才发现我可以连接的APNs地址...

gateway.sandbox.push.apple.com:2195

gateway.push.apple.com:2195

没有进一步的... ...

class APNsTest(BaseRPCHandler):

  def get(self, context, name):
    self._call_method(context, name)

  def send_push(self):

    # certificate files
    abs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../cert'))
    pem_file = os.path.abspath(os.path.join(abs_path, 'VisitorGuide.pem'))
    ca_certs = '/etc/ca-certificates.crt'

    # APNS server address...
    apns_address = ('gateway.sandbox.push.apple.com', 2195)
    # apns_address = ('gateway.push.apple.com', 2195)

    # a socket to connect to APNS over SSL
    _sock = socket.socket()
    _ssl = ssl.wrap_socket(_sock, certfile=pem_file,
                                  server_side=False,
                                  cert_reqs=ssl.CERT_REQUIRED,
                                  ssl_version=ssl.PROTOCOL_TLSv1,
                                  ca_certs=ca_certs)
    _ssl.connect(apns_address)

    # a notification packet
    payload = json.dumps(PAYLOAD)
    token = binascii.unhexlify(TOKEN)
    fmt = '!cH32sH{0:d}s'.format(len(payload))
    cmd = '\x00'
    message = struct.pack(fmt, cmd, len(token), token, len(payload), payload)

    _ssl.write(message)
    _ssl.close()

    return self.response_result(PAYLOAD)

...执行没有错误。

答案 2 :(得分:0)

此外,通过实验,我发现当我应用内置的第三方ssl lib时,App Engine上的SSL相关错误通常会消失:

libraries:
- name: ssl
  version: latest

或:

libraries:
- name: ssl
  version: "2.7"