基于 GAE 的 iOS 推送通知 (APN)、SSL 握手失败

Posted

技术标签:

【中文标题】基于 GAE 的 iOS 推送通知 (APN)、SSL 握手失败【英文标题】:iOS Push Notifications (APNs) over GAE, SSL Handshake Failure 【发布时间】:2016-06-18 00:36:16 【问题描述】:

我正在尝试使用此 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 = '!cH32sH0:ds'.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 OpenSSL 和 TLS/SSL wrapper for socket objects,更不用说一些 *** 帖子了。

请提供有关适当的密钥文件、证书文件和 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 等其他文件会导致返回到原始握手失败。

【问题讨论】:

使用 Pusher 应用程序我能够确认我的 PEM 文件有问题。 Pusher 获取到原始 .p12 文件后,就可以获取到 APNs 连接了。 【参考方案1】:

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

【讨论】:

计费已启用,已验证,已经有一段时间了。现在与 Pusher 一起工作,这非常有帮助,因为 Pusher 应用程序抱怨私钥(非常有用的反馈)。【参考方案2】:

相应的支持文件以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 ~ entrust_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 和 APNs 之间的 SSL 连接)

根据 iOS 开发者库APNs Provider API

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

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

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

注意:与 APNs 通信时,您也可以使用端口 2197。例如,您可以这样做,以允许 APNs 流量通过您的防火墙,但阻止其他 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 = '!cH32sH0:ds'.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)

...执行没有错误。

【讨论】:

【参考方案3】:

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

libraries:
- name: ssl
  version: latest

或:

libraries:
- name: ssl
  version: "2.7"

【讨论】:

好建议,我一直在我的 app.ymal 中使用 -name: ssl version: "2.7" 表示法,这个处理程序正在读取导入 ssl 而没有错误。 握手失败? ...继续,或者被替换为“SSLError: [Errno 0] _ssl.c:343: error:00000000:lib(0):func(0):reason(0)”我遇到的问题似乎集中在证书上。

以上是关于基于 GAE 的 iOS 推送通知 (APN)、SSL 握手失败的主要内容,如果未能解决你的问题,请参考以下文章

为 iOS 发送 9000/s 推送通知

我是不是需要注册 APN 才能在 iOS 中使用 GCM 推送通知?

通过 FCM 发送时未收到推送通知,但在 IOS 上通过 APN 发送时收到

通过 FCM 发送推送通知时何时使用 iOS 设备令牌?

Spring项目中APN的休息控制器(推送通知)

Cordova 推送通知 ios