Skip to content

Commit 686c695

Browse files
authored
Tls cert feature (#47)
* Attempt to let token auth be used. * Remove unsed Signer code. # Conflicts: # Sources/APNSwiftPemExample/main.swift * Adds Pem Example. # Conflicts: # Sources/APNSwiftPemExample/main.swift * Refactor negotiation * update example path. * device token update.
1 parent 33717cb commit 686c695

File tree

6 files changed

+150
-32
lines changed

6 files changed

+150
-32
lines changed

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ let package = Package(
2323
),
2424

2525
.target(name: "APNSwiftExample", dependencies: ["APNSwift"]),
26+
.target(name: "APNSwiftPemExample", dependencies: ["APNSwift"]),
2627
.testTarget(name: "APNSwiftJWTTests", dependencies: ["APNSwift"]),
2728
.testTarget(name: "APNSwiftTests", dependencies: ["APNSwift"]),
2829
.target(name: "APNSwift", dependencies: ["NIO",
2930
"NIOSSL",
3031
"NIOHTTP1",
3132
"NIOHTTP2",
3233
"NIOFoundationCompat",
33-
"CAPNSOpenSSL"]),
34+
"CAPNSOpenSSL",
35+
"NIOTLS"]),
3436
]
3537
)

Sources/APNSwift/APNSwiftConfiguration.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public struct APNSwiftConfiguration {
6363
self.topic = topic
6464
self.signer = signer
6565
self.environment = environment
66-
tlsConfiguration = TLSConfiguration.forClient(applicationProtocols: ["h2"])
66+
self.tlsConfiguration = TLSConfiguration.forClient(applicationProtocols: ["h2"])
67+
6768
}
6869
}
6970

Sources/APNSwift/APNSwiftConnection.swift

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,48 @@ import Foundation
1616
import NIO
1717
import NIOHTTP2
1818
import NIOSSL
19+
import NIOTLS
20+
21+
private final class WaitForTLSUpHandler: ChannelInboundHandler {
22+
typealias InboundIn = Never
23+
24+
struct TLSNegotiationError: Error {
25+
var wrongProtocolNegotiated: String?
26+
}
27+
28+
private let allDonePromise: EventLoopPromise<Void>
29+
30+
init(allDonePromise: EventLoopPromise<Void>) {
31+
self.allDonePromise = allDonePromise
32+
}
33+
34+
func errorCaught(context: ChannelHandlerContext, error: Error) {
35+
// this is an unknown error, this is unexpected, let's fail everything and close the connection.
36+
self.allDonePromise.fail(error)
37+
context.close(promise: nil)
38+
}
39+
40+
func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {
41+
if let event = event as? TLSUserEvent {
42+
switch event {
43+
case .handshakeCompleted(negotiatedProtocol: "h2"):
44+
self.allDonePromise.succeed(())
45+
case .handshakeCompleted(negotiatedProtocol: let proto):
46+
self.allDonePromise.fail(TLSNegotiationError(wrongProtocolNegotiated: proto))
47+
context.close(promise: nil)
48+
case .shutdownCompleted:
49+
context.fireUserInboundEventTriggered(event)
50+
}
51+
} else {
52+
context.fireUserInboundEventTriggered(event)
53+
}
54+
}
55+
56+
func channelInactive(context: ChannelHandlerContext) {
57+
// there's always the possibility that we just get a close which we need to handle.
58+
self.allDonePromise.fail(ChannelError.eof)
59+
}
60+
}
1961

2062
public final class APNSwiftConnection {
2163

@@ -39,26 +81,25 @@ public final class APNSwiftConnection {
3981
*/
4082

4183
public static func connect(configuration: APNSwiftConfiguration, on eventLoop: EventLoop) -> EventLoopFuture<APNSwiftConnection> {
42-
let bootstrap = ClientBootstrap(group: eventLoop)
43-
.channelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
44-
.channelInitializer { channel in
45-
do {
46-
let sslContext = try NIOSSLContext(configuration: configuration.tlsConfiguration)
47-
let sslHandler = try NIOSSLClientHandler(context: sslContext, serverHostname: configuration.url.host)
48-
return channel.pipeline.addHandler(sslHandler).flatMap {
49-
channel.configureHTTP2Pipeline(mode: .client) { _, _ in
50-
fatalError("server push not supported")
51-
}.map { _ in }
84+
struct UnsupportedServerPushError: Error {}
85+
86+
let sslContext = try! NIOSSLContext(configuration: configuration.tlsConfiguration)
87+
let connectionFullyUpPromise = eventLoop.makePromise(of: Void.self)
88+
89+
let tcpConnection = ClientBootstrap(group: eventLoop).connect(host: configuration.url.host!, port: 443)
90+
tcpConnection.cascadeFailure(to: connectionFullyUpPromise)
91+
return tcpConnection.flatMap { channel in
92+
let sslHandler = try! NIOSSLClientHandler(context: sslContext,
93+
serverHostname: configuration.url.host)
94+
return channel.pipeline.addHandlers([sslHandler,
95+
WaitForTLSUpHandler(allDonePromise: connectionFullyUpPromise)]).flatMap {
96+
channel.configureHTTP2Pipeline(mode: .client) { channel, _ in
97+
return channel.eventLoop.makeFailedFuture(UnsupportedServerPushError())
98+
}.flatMap { multiplexer in
99+
connectionFullyUpPromise.futureResult.map {
100+
return APNSwiftConnection(channel: channel, multiplexer: multiplexer, configuration: configuration)
52101
}
53-
} catch {
54-
channel.close(mode: .all, promise: nil)
55-
return channel.eventLoop.makeFailedFuture(error)
56102
}
57-
}
58-
59-
return bootstrap.connect(host: configuration.url.host!, port: 443).flatMap { channel in
60-
return channel.pipeline.handler(type: HTTP2StreamMultiplexer.self).map { multiplexer in
61-
return APNSwiftConnection(channel: channel, multiplexer: multiplexer, configuration: configuration)
62103
}
63104
}
64105
}
@@ -117,7 +158,6 @@ public final class APNSwiftConnection {
117158
)
118159

119160
return streamPromise.futureResult.flatMap { stream in
120-
responsePromise.futureResult.whenComplete { _ in }
121161
return stream.writeAndFlush(context)
122162
}.flatMap {
123163
responsePromise.futureResult

Sources/APNSwift/APNSwiftRequestEncoder.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,15 @@ internal final class APNSwiftRequestEncoder: ChannelOutboundHandler {
7777
reqHead.headers.add(name: "apns-push-type", value: pushType.rawValue)
7878
}
7979
reqHead.headers.add(name: "host", value: configuration.url.host!)
80-
guard let token = bearerToken.token else {
81-
promise?.fail(APNSwiftError.SigningError.invalidSignatureData)
82-
return
80+
// Only use token auth if private key is nil
81+
if configuration.tlsConfiguration.privateKey == nil {
82+
guard let token = bearerToken.token else {
83+
promise?.fail(APNSwiftError.SigningError.invalidSignatureData)
84+
return
85+
}
86+
reqHead.headers.add(name: "authorization", value: "bearer \(token)")
8387
}
84-
reqHead.headers.add(name: "authorization", value: "bearer \(token)")
88+
8589
context.write(wrapOutboundOut(.head(reqHead))).cascadeFailure(to: promise)
8690
context.write(wrapOutboundOut(.body(.byteBuffer(buffer)))).cascadeFailure(to: promise)
8791
context.write(wrapOutboundOut(.end(nil)), promise: promise)

Sources/APNSwiftExample/main.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import NIOSSL
2222
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
2323
var verbose = true
2424

25-
let signer = try! APNSwiftSigner(filePath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8")
25+
let signer = try! APNSwiftSigner(filePath: "/Users/kylebrowning/Desktop/AuthKey_9UC9ZLQ8YW.p8")
2626

2727
let apnsConfig = APNSwiftConfiguration(keyIdentifier: "9UC9ZLQ8YW",
2828
teamIdentifier: "ABBM6U9RM5",
@@ -56,12 +56,7 @@ let token = APNSwiftBearerToken(configuration: apnsConfig, timeout: 50.0)
5656

5757
do {
5858
let expiry = Date().addingTimeInterval(5)
59-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
60-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
61-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
62-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
63-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
64-
try apns.send(notification, bearerToken: token, to: "b693f99efa926bcf9977adae75bc88a97988365f49e4c6abf94aa659a4d87a6b", expiration: expiry, priority: 10).wait()
59+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
6560
} catch {
6661
print(error)
6762
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the APNSwift open source project
4+
//
5+
// Copyright (c) 2019 the APNSwift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of APNSwift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import Foundation
16+
import NIO
17+
import APNSwift
18+
import NIOHTTP1
19+
import NIOHTTP2
20+
import NIOSSL
21+
22+
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
23+
var verbose = true
24+
25+
//let signer = try! APNSwiftSigner(filePath: "/Users/kylebrowning/Downloads/AuthKey_9UC9ZLQ8YW.p8")
26+
27+
var apnsConfig = try APNSwiftConfiguration(keyIdentifier: "9UC9ZLQ8YW",
28+
teamIdentifier: "ABBM6U9RM5",
29+
signer: APNSwiftSigner.init(buffer: ByteBufferAllocator().buffer(capacity: Data().count)),
30+
topic: "com.grasscove.Fern",
31+
environment: .sandbox)
32+
33+
let key = try NIOSSLPrivateKey(file: "/Users/kylebrowning/Projects/swift/Fern/development_com.grasscove.Fern.pkey", format: .pem)
34+
apnsConfig.tlsConfiguration.privateKey = NIOSSLPrivateKeySource.privateKey(key)
35+
apnsConfig.tlsConfiguration.certificateVerification = .noHostnameVerification
36+
apnsConfig.tlsConfiguration.certificateChain = try! [.certificate(.init(file: "/Users/kylebrowning/Projects/swift/Fern/development_com.grasscove.Fern.pem", format: .pem))]
37+
38+
let apns = try APNSwiftConnection.connect(configuration: apnsConfig, on: group.next()).wait()
39+
40+
if verbose {
41+
print("* Connected to \(apnsConfig.url.host!) (\(apns.channel.remoteAddress!)")
42+
}
43+
44+
struct AcmeNotification: APNSwiftNotification {
45+
let acme2: [String]
46+
let aps: APNSwiftPayload
47+
48+
init(acme2: [String], aps: APNSwiftPayload) {
49+
self.acme2 = acme2
50+
self.aps = aps
51+
}
52+
}
53+
54+
let alert = APNSwiftPayload.APNSwiftAlert(title: "Hey There", subtitle: "Subtitle", body: "Body")
55+
let apsSound = APNSwiftPayload.APNSSoundDictionary(isCritical: true, name: "cow.wav", volume: 0.8)
56+
let aps = APNSwiftPayload(alert: alert, badge: 0, sound: .critical(apsSound), hasContentAvailable: true)
57+
let temp = try! JSONEncoder().encode(aps)
58+
let string = String(bytes: temp, encoding: .utf8)
59+
let notification = AcmeNotification(acme2: ["bang", "whiz"], aps: aps)
60+
let token = APNSwiftBearerToken(configuration: apnsConfig, timeout: 50.0)
61+
62+
do {
63+
let expiry = Date().addingTimeInterval(5)
64+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
65+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
66+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
67+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
68+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
69+
try apns.send(notification, bearerToken: token, to: "98AAD4A2398DDC58595F02FA307DF9A15C18B6111D1B806949549085A8E6A55D", expiration: expiry, priority: 10).wait()
70+
} catch {
71+
print(error)
72+
}
73+
74+
try apns.close().wait()
75+
try group.syncShutdownGracefully()
76+
exit(0)

0 commit comments

Comments
 (0)