I am trying to implement an iOS proxy running inside the network extension to log the packets.
I am referring the connect-proxy example which relays the data properly.
From what I have read, to capture packet data, I'll have to install and trust a root certificate on iOS device and use that but am kinda stuck at this.
I generated my self-signed certificate with:
openssl genrsa -passout pass:my_pass -out key.pem 8192
openssl req -new -x509 -key key.pem -out root-ca.pem
And installed & trusted it on my iOS device.
My local proxy configuration in the extension looks something like:
private func startLocalProxy() {
logger.logLevel = .debug
let bundle = Bundle(for: type(of: self))
guard let certificatePath = bundle.path(forResource: "root-ca", ofType: "pem"),
let keyPath = bundle.path(forResource: "key", ofType: "pem"),
let certChain = (try? NIOSSLCertificate.fromPEMFile(certificatePath).map{ NIOSSLCertificateSource.certificate($0) }) else {
logger.error("Failed to setup certificate chain")
self.pendingCompletion?(NEVPNError(.connectionFailed))
return
}
let sslPrivateKey = try! NIOSSLPrivateKeySource.privateKey(NIOSSLPrivateKey(file: keyPath, format: .pem) { providePassword in
providePassword("my_pass".utf8)
})
var serverConfiguration = TLSConfiguration.makeServerConfiguration( certificateChain: certChain, privateKey: sslPrivateKey)
serverConfiguration.applicationProtocols = NIOHTTP2SupportedALPNProtocols
let serverContext = try! NIOSSLContext(configuration: serverConfiguration)
var clientConfiguration = TLSConfiguration.makeClientConfiguration()
clientConfiguration.certificateVerification = .none
let clientContext = try! NIOSSLContext(configuration: clientConfiguration)
self.startBasicProxy(serverContext: serverContext, clientContext: clientContext)
}
private func startBasicProxy(serverContext: NIOSSLContext, clientContext: NIOSSLContext) {
let bootstrap = ServerBootstrap(group: localProxyGroup)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes)), name: "http-request-decoder")
.flatMap { channel.pipeline.addHandler(HTTPResponseEncoder(), name: "http-response-encoder") }
.flatMap { channel.pipeline.addHandler(ConnectHandler(logger: Logger(label: "com.apple.nio-connect-proxy.ConnectHandler"), serverContext: serverContext, clientContext: clientContext), name: "connect-handler") }
.flatMap { channel.pipeline.addHandler(ErrorHandler(), name: "error-handler") }
}
bootstrap.bind(host: configuration.hostname, port: Int(configuration.port)!).whenComplete { result in
// Need to create this here for thread-safety purposes
switch result {
case .success(let channel):
self.logger.info("Listening on \(String(describing: channel.localAddress))")
self.startTunnel()
case .failure(let error):
self.logger.error("Failed to bind 127.0.0.1:8028, \(error)")
self.pendingCompletion?(NEVPNError(.connectionFailed))
self.pendingCompletion = nil
}
}
}
And inside ConnectHandler, I also add the TLS handler after CONNECT like:
private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {
self.logger.debug("Gluing together \(ObjectIdentifier(context.channel)) and \(ObjectIdentifier(peerChannel))")
// Ok, upgrade has completed! We now need to begin the upgrade process.
// First, send the 200 message.
// This content-length header is MUST NOT, but we need to workaround NIO's insistence that we set one.
let headers = HTTPHeaders([("Content-Length", "0")])
let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .ok, headers: headers)
context.write(self.wrapOutboundOut(.head(head)), promise: nil)
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
// Now remove the HTTP encoder.
self.removeEncoder(context: context)
context.pipeline.removeHandler(self, promise: nil)
// Upgrade to tls
let tlsHandler = NIOSSLServerHandler(context: serverContext)
context.channel.pipeline.addHandler(tlsHandler, name: "tls-handler", position: .first).whenComplete { result1 in
// Now we need to glue our channel and the peer channel together.
let (localGlue, peerGlue) = GlueHandler.matchedPair()
context.channel.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).whenComplete { result in
switch result {
case .success(_):
print("Added handler")
case .failure(_):
// Close connected peer channel before closing our channel.
peerChannel.close(mode: .all, promise: nil)
context.close(promise: nil)
}
}
}
}
I also add a SSL handler for the client:
private func connectTo(host: String, port: Int, context: ChannelHandlerContext) {
self.logger.info("Connecting to \(host):\(port)")
let channelFuture = ClientBootstrap(group: context.eventLoop)
.channelInitializer { channel in
channel.pipeline.addHandlers(try! NIOSSLClientHandler(context: self.clientContext, serverHostname: host),
CloseOnErrorHandler(logger: self.logger))
}
.connect(host: String(host), port: port)
channelFuture.whenSuccess { channel in
self.connectSucceeded(channel: channel, context: context)
}
channelFuture.whenFailure { error in
self.connectFailed(error: error, context: context)
}
}
But I keep getting handshakeFailed whenever I try to load a page in Safari.
Can someone point me in the right direction? Been banging my head against the wall
Would appreciate any help!