Skip to content
This repository has been archived by the owner on Dec 22, 2023. It is now read-only.

Commit

Permalink
Find default APNS topic from APNS certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
cheungpat committed Jul 8, 2016
1 parent 88ad563 commit baa3467
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 8 deletions.
65 changes: 64 additions & 1 deletion push/apns.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package push

import (
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -49,13 +51,63 @@ type APNSPusher struct {
conn skydb.Conn
service pushService
failed chan failedNotification
topic string
}

type failedNotification struct {
deviceToken string
err push.Error
}

// parseCertificateLeaf parse the provided TLS certificate for its
// leaf certificate. Returns an error if the leaf certificate cannot be found.
func parseCertificateLeaf(certificate *tls.Certificate) error {
if certificate.Leaf != nil {
return nil
}

for _, cert := range certificate.Certificate {
x509Cert, err := x509.ParseCertificate(cert)
if err != nil {
return err
}
certificate.Leaf = x509Cert
return nil
}
return errors.New("push/apns: provided APNS certificate does not contain leaf")
}

// findDefaultAPNSTopic returns the APNS topic in the TLS certificate.
//
// The Subject of leaf certificate should contains the UID, which we can
// use as the topic for APNS. The topic is usually the same as the
// application bundle // identifier.
//
// Returns the topic name, and an error if an error occuring finding the topic
// name.
func findDefaultAPNSTopic(certificate tls.Certificate) (string, error) {
if certificate.Leaf == nil {
err := parseCertificateLeaf(&certificate)
if err != nil {
return "", err
}
}

// Loop over the subject names array to look for UID
uidObjectIdentifier := asn1.ObjectIdentifier([]int{0, 9, 2342, 19200300, 100, 1, 1})
for _, attr := range certificate.Leaf.Subject.Names {
if uidObjectIdentifier.Equal(attr.Type) {
switch value := attr.Value.(type) {
case string:
return value, nil
}
break
}
}

return "", errors.New("push/apns: cannot find UID in APNS certificate subject name")
}

// NewAPNSPusher returns a new APNSPusher from content of certificate
// and private key as string
func NewAPNSPusher(connOpener func() (skydb.Conn, error), gwType GatewayType, cert string, key string) (*APNSPusher, error) {
Expand All @@ -64,6 +116,11 @@ func NewAPNSPusher(connOpener func() (skydb.Conn, error), gwType GatewayType, ce
return nil, err
}

topic, err := findDefaultAPNSTopic(certificate)
if err != nil {
return nil, err
}

client, err := push.NewClient(certificate)
if err != nil {
return nil, err
Expand All @@ -82,6 +139,7 @@ func NewAPNSPusher(connOpener func() (skydb.Conn, error), gwType GatewayType, ce
return &APNSPusher{
connOpener: connOpener,
service: service,
topic: topic,
}, nil
}

Expand Down Expand Up @@ -163,6 +221,7 @@ func (pusher *APNSPusher) Send(m Mapper, device skydb.Device) error {
logger := log.WithFields(log.Fields{
"deviceToken": device.Token,
"deviceID": device.ID,
"apnsTopic": pusher.topic,
})

if m == nil {
Expand All @@ -180,8 +239,12 @@ func (pusher *APNSPusher) Send(m Mapper, device skydb.Device) error {
return err
}

headers := push.Headers{
Topic: pusher.topic,
}

// push the notification:
apnsid, err := pusher.service.Push(device.Token, nil, serializedPayload)
apnsid, err := pusher.service.Push(device.Token, &headers, serializedPayload)
if err != nil {
if pushError, ok := err.(*push.Error); ok && pushError != nil {
// We recognize the error, and that error comes from APNS
Expand Down
63 changes: 56 additions & 7 deletions push/apns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package push

import (
"crypto/tls"
"errors"
"net/http"
"testing"
Expand All @@ -24,7 +25,6 @@ import (
"github.com/skygeario/skygear-server/skydb"
. "github.com/skygeario/skygear-server/skytest"
. "github.com/smartystreets/goconvey/convey"
"github.com/timehop/apns"
)

type naiveServiceNotification struct {
Expand Down Expand Up @@ -187,12 +187,6 @@ func (c *mockConn) Open() (skydb.Conn, error) {
return c, nil
}

type feedbackChannel chan apns.FeedbackTuple

func (ch feedbackChannel) Receive() <-chan apns.FeedbackTuple {
return ch
}

func TestAPNSFeedback(t *testing.T) {
Convey("APNSPusher", t, func() {
conn := &mockConn{}
Expand Down Expand Up @@ -254,3 +248,58 @@ func TestAPNSFeedback(t *testing.T) {
})
})
}

func TestAPNSCertificate(t *testing.T) {
APNSCert := []byte(`-----BEGIN CERTIFICATE-----
MIICvjCCAaYCCQDrgz2ANVkBfTANBgkqhkiG9w0BAQsFADAhMR8wHQYKCZImiZPy
LGQBAQwPY29tLmV4YW1wbGUuQXBwMB4XDTE2MDcwODA3NTAzN1oXDTE3MDcwODA3
NTAzN1owITEfMB0GCgmSJomT8ixkAQEMD2NvbS5leGFtcGxlLkFwcDCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKd+cBNZ9lyKb81WGbNU4Vh4Z5TJB0tI
V7m3Iohl0sd8zbUhL8ISXPpKnPvwo2DZScs6Y4hJsZxQenNm5ll4cFAgcNHbu0I6
T1VzLSnxtpgvDOdxOBN5Nw1syKfzMUJw8o8RMtRt9cYVBwlKvOI92agFqZVYCIA3
4T/531f/VejIFd8wzp8fLMS+A8dJ+Run9Z4r4KZu8VhtKUP8GAFZ0pt9PL4Rm4Rl
/J/FZi5EmCE9Ms1RZoLuwO/IKPuGIY5rRi1c0kbYL3+QPxlkJa9DGW/61mDEkPkx
l6MvrbBFQIDSRUVQ97a8RNk/5tBwsAnyyqYxx9i9wjudNCv5YQs/QL8CAwEAATAN
BgkqhkiG9w0BAQsFAAOCAQEAb1mm4+6B+8YFqagAQ18I8EzOIHqrceDHj+v3PAh7
jD+orKmbFnq6kbzEj9AHOp+A5EjSLIBIarXFJIsbRXenYwDLF+0dwFkXzzhLSsAO
kvsTQPFaQC/h3mV8stx2SLxTDpWMPaaNCOlPkTmEtqXA3fes/1hF6TYalYO6kHe8
47iuzxKNjgfjjYeK3o4ccFS0+29WVoU5t+wuZ0Ha27PPNOFHLvn9TI9A5L+8ujgr
oyxSZaLz1oPX7aCcC847s+a73+K4V4QwdvKhxEN2McdZqv1h1Ha5zptt4kniBv6X
OFCnXiurw3uY37eBckl/JR++IkUekyIq1EJ0vfWyW/mhPQ==
-----END CERTIFICATE-----`)
APNSKey := []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAp35wE1n2XIpvzVYZs1ThWHhnlMkHS0hXubciiGXSx3zNtSEv
whJc+kqc+/CjYNlJyzpjiEmxnFB6c2bmWXhwUCBw0du7QjpPVXMtKfG2mC8M53E4
E3k3DWzIp/MxQnDyjxEy1G31xhUHCUq84j3ZqAWplVgIgDfhP/nfV/9V6MgV3zDO
nx8sxL4Dx0n5G6f1nivgpm7xWG0pQ/wYAVnSm308vhGbhGX8n8VmLkSYIT0yzVFm
gu7A78go+4YhjmtGLVzSRtgvf5A/GWQlr0MZb/rWYMSQ+TGXoy+tsEVAgNJFRVD3
trxE2T/m0HCwCfLKpjHH2L3CO500K/lhCz9AvwIDAQABAoIBAQCgZYyee4BZjpkS
YmmqOpaySlunN/wsM9MOnjoLtLbtIq87zdQWXc98QQeknQVYMb1hSUEXurrDnq4k
5V2iQJwNn4Nq9KmW+pAOnIWbrUXW5vfMi7fPrjzyNkLR0ypRHiiqqSWsGMFMN8bN
Ny0621Acf4+u3OcHInwq7/baJkL271g1m0hX/7TJ/nv+SlO00IkB6tm8iU0LWant
4fSvhV2ULxa0fF5XXx74jqDFF+NzJ45XMUDbbe72RKpydMsdprqp4BgaV7fbtHIx
xjG/9z6KqM3v0bMkJFV9BnmWzNe7vrI96dizVR3w3Yygul7sNC2iZfe17ulaXXKA
n3X+PBTBAoGBANkwAMW+dQw9x/dDIQgfkg27I7qgANdYyri/lJ6+GvMdcCd4mzQ+
UnJzhPNJyPZSUZeHOthShbHvBcoYNi4AwEcor2bXJI3+jW3/Wqbpq87+9x3Hkk69
tkKvESKy8IABOTntFD3/VGFjYiLXVpg0huvkRSlSv70gMusmTMxisvWNAoGBAMVt
Bx9IVkZ4uFl565WmigUuO8ICgHeRovCqeF3g0YBlS/x9Jznd9QkMYT0hy9cE4b7Z
GRm89mEOUclWgdsibkyEdD1qOF6xF+fi4xKaes8Vc3vuuhg+UqpYvbIxKFAT81A2
adyL6npDJDqQ0DcRjMng8V/77ktLOe/g9HFLX957AoGAOIlKai9eAMXEXBVZb+fn
+TMR5e7oySYP/2+/nGMYWNj87Ql0PXFLvQddQIegjJ55JtzI8K7qppr2AtmyoN8J
Lnzky/yNQ3lUD6I9Ut3ZH5U3dsUQzPaNj2ZLK6ExAeFPqEiS0GC68m8QiMlNfWmP
BbDyYANubikHmDbsHvhCZbECgYA0hx62/wsdcu8xt1OsHIRqfnOd2gaOSax9tg2S
hMeZDtqZ0j7GkbypbKbOmhhfHEhn++FGzNUM2798/0xLnqyUJUW8NW/MGfhPVTmv
cHSudnmkhs7ytlpOQpAuQhAExlodhGzEJmH7p7OS9YbAsCWybOwr6p7rX5eJsGO5
ZSGb0wKBgQCvYtVVUGPBbmp94yoRgQjPj+iZtSjmVA9LaNoX+g4UWxKTU3y+r9kg
SZVlxzxtvFvO+LcxmP6wBSX30HmhtIFLxmOyySl6BjfbtE0uFnIxxrKhb2L0gqoN
g723fJntDb71I1IS31Vd2wqqpVB4kDp8OiPnPp8ats/cNUFk77Jhxw==
-----END RSA PRIVATE KEY-----`)

Convey("find apns topic", t, func() {
certificate, err := tls.X509KeyPair(APNSCert, APNSKey)
So(err, ShouldBeNil)
topic, err := findDefaultAPNSTopic(certificate)
So(err, ShouldBeNil)
So(topic, ShouldEqual, "com.example.App")
})
}

0 comments on commit baa3467

Please sign in to comment.