Ktor client IOS and self-signed certificate - ios

I'm using Ktor and Kotlin/native in iOS in an iOS app that accesses an internal dev server. The dev server uses a certificate issued by an internal CA which is not publicly trusted.
When trying to access the server with the following code :
internal suspend fun performHttp(url : String)
{
// URL is a self signed HTTPS: request
val client = HttpClient(Ios)
val response = client.get<String>(url)
println(response)
}
it throws the following exception :
TIC SSL Trust Error [32:0x281956dc0]: 3:0
esri2[470:136341] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9807)
esri2[470:136341] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> HTTP load failed (error code: -1202 [3:-9807])
esri2[470:136417] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> finished with error - code: -1202
esri2[470:136211] Task <F3CC4C40-0231-4E58-97F3-F457D5A18BB0>.<1> load failed with error Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “server1.internal.lan” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
"<cert(0x12b094e00) s: server1.internal.lan i: Internal-Issuing-CA2>",
How do I convince Ktor that it should access this URL, or ignore untrusted certs? Yes, I know that one should not ignore untrusted certs, but this is a lab test.

Ktor iOS engine offers the ability to configure the underlying NSURLSession with the help of IosClientEngineConfig.kt.
With it you can configure (amongst other things) a ChallengeHandler by setting the block for handleChallenge in the config like this:
val client = HttpClient(Ios) {
engine {
handleChallenge(TrustSelfSignedCertificate())
}
}
Then you need to implement a class in Kotlin something like this:
internal data class TrustSelfSignedCertificate internal constructor(
private val validateTrust: Boolean = true
) : ChallengeHandler {
override fun invoke(
session: NSURLSession,
task: NSURLSessionTask,
challenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Unit
) {
val hostname = challenge.protectionSpace.host
val serverTrust = challenge.protectionSpace.serverTrust
var result: SecTrustResultType = 0u
memScoped {
val nativeResult = alloc<SecTrustResultTypeVar>()
nativeResult.value = result
SecTrustEvaluate(serverTrust!!, nativeResult.ptr)
}
val serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0)
val serverCertificateData = SecCertificateCopyData(serverCertificate)
val data = CFDataGetBytePtr(serverCertificateData)
val size = CFDataGetLength(serverCertificateData)
val cert1 = NSData.dataWithBytes(data, size.toULong())
val pathToCert = NSBundle.mainBundle.pathForResource("myOwnCert", "cer")
val localCertificate: NSData = NSData.dataWithContentsOfFile(pathToCert!!)!!
if (localCertificate == cert1) {
completionHandler(
NSURLSessionAuthChallengeUseCredential,
NSURLCredential.create(serverTrust)
)
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, null)
}
}
}
Also, don't forget to put you certificate as a file "myOwnCert.cer" into you iOS project (maybe on the top-level).
NOTE
Ktor with iOS engine does not respect/use NSApptransportSecurity.
The code is based on this answers.
With the help of this blog-post.

Related

GRPC-Swift Send HTTPProtocolVersion from Client

I'm trying GRPC-Swift for Client-Server application.
I'm using GRPC-Swift for both Client and Server
Client is an iPhone application, which I tried with iPhone Simulator.
I followed this link for Client-side streaming RPC.
When I send message to Server from Client, I got the following error message in the console from Server,
error io.grpc.server_channel_call : unable to determine http version
From the Server in the
HTTPProtocolSwitcher.swift
inside the function func channelRead(context: ChannelHandlerContext, data: NIOAny), it is checking for HTTPProtocolVersion, and it is missing.
How to send the HTTPVersion from the Client code?
Update:
Client Code
import GRPC
import NIO
class HTTPClient {
private let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
private var channel: ClientConnection?
private var client: ChatGuide_ChatGuideClient?
private var clientCall: ClientStreamingCall<ChatGuide_TextMessage, ChatGuide_TextMessage>?
func connect(host: String, port: Int) throws {
let channel = ClientConnection.secure(group: self.group)
.connect(host: host, port: port)
self.channel = channel
self.client = ChatGuide_ChatGuideClient(channel: channel)
}
func disconnect() {
do {
self.clientCall?.sendEnd(promise: nil)
_ = try self.clientCall?.status.wait()
try self.group.syncShutdownGracefully()
} catch let error {
print("\(type(of: self)): Could not shutdown gracefully -", error.localizedDescription)
}
}
func initiateClient() {
let timeAmount = TimeAmount.minutes(1)
let timeLimit = TimeLimit.timeout(timeAmount)
let options = CallOptions(timeLimit: timeLimit)
let call = self.client?.chat(callOptions: options)
call?.response.whenSuccess { (message) in
print("\(type(of: self)): Message from server -", message.text)
}
call?.response.whenFailure { (error) in
print("\(type(of: self)): Response error -", error.localizedDescription)
}
self.clientCall = call
}
func send(text: String) {
if self.clientCall == nil {
self.initiateClient()
}
let message = ChatGuide_TextMessage.with {
$0.text = text
}
self.clientCall?.sendMessage(message, promise: nil)
}
}
Hey Vignesh,
I am currently learning gRPC-Swift myself, so I hope I will be of service and not muck things further.
However, it looks to me that you are not configuring the HTTP/1.x layer in order to transfer Protobuf packets, if you take a look at the HTTP1ToGRPCServerCodec.swift file Here
I think you will have a much clearer idea of how to adjust your code, I am sorry I can't provide more details, however not being too sure myself without further testing and reviewing the codebase.
Best regards and keep me posted if indeed i was helpful,
cheers
From the Server I have initiated insecure Server as,
let server = Server.insecure(group: self.group)
From the Client I have initiated secure ClientConnection as,
let channel = ClientConnection.secure(group: self.group)
And I got this clarification from here
So I made the ClientConnection also insecure as,
let channel = ClientConnection.insecure(group: self.group)
And after this it is working now.

Unable to get response from server

Unable to hit the server through iPhone Simulator, But able to hit the same API through a browser (with proxies enabled in network settings).
There is no trusted certificate.
Error Log
nw_proxy_resolver_create_parsed_array PAC evaluation error: NSURLErrorDomain: -1003
NSURLSession/NSURLConnection HTTP load failed
The certificate for this server is invalid. You might be connecting to a server that is pretending to be "domain name"...
TIC SSL Trust Error
Try adding at the end of your Appdelegate class after #end,
Objective-C
#implementation NSURLRequest(ATS)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host {
return YES;
}
#end
Swift
extension URLRequest {
static func allowsAnyHTTPSCertificate(forHost host: String?) -> Bool {
return true
}
}

iOS: SSL Handshake over bluetooth

I'm trying to do a SSL Handshake over Bluetooth. I've been trying to use the Secure Transport API (https://developer.apple.com/documentation/security/secure_transport) to achieve this:
func sslHandshake() {
guard let sslContext = SSLCreateContext(kCFAllocatorDefault, .clientSide, .streamType) else {
return
}
SSLSetIOFuncs(sslContext, { (connection, data, dataLength) -> OSStatus in
//Read
return noErr
}) { (connection, data, dataLength) -> OSStatus in
//Write
writeToPeripheral(data)
return noErr
}
SSLHandshake(sslContext)
}
I've been able to write the client hello data to the bluetooth peripheral without a problem. But before I can write the server hello that I get from the peripheral, the SSL handshake fails and when I try doing a SSLRead I get an error -9806.
Is there a way I can avoid the handshake from failing so I can write the server hello, cert and other stuff for the handshake?

How to connect using Socket-IO-Swift on a self-signed certificate?

I use the framework Socket.IO-cleint-swift. Below is the code for creating and connecting:
let socket = SocketIOClient.init(socketURL: URL(string: "https://ts4.steelsea.net:8080")!, config: [.log(true), .forcePolling(true),.secure(true),.selfSigned(true)])
socket.on("connect") {data, ack in
print("socket connected")
}
socket.on("currentAmount") {data, ack in
if let cur = data[0] as? Double {
socket.emitWithAck("canUpdate", cur).timingOut(after: 0) {data in
socket.emit("update", ["amount": cur + 2.50])
}
ack.with("Got your currentAmount", "dude")
}
}
socket.connect()
Below is an error:
ERROR SocketIOClient: The certificate for this server is invalid. You
might be connecting to a server that is pretending to be
“ts4.steelsea.net” which could put your confidential information at
risk.
I can not figure out how to confirm this certificate to me.
This is connected with insecure connection with the server. And that is why Socket.IO is sending you error.
If you would like to silent this error you can try to drop secure(true) and selfSigned(true) configs.

How do I configure identityData of NEVPNProtocolIKEv2 with String certificate?

I using NetworkExtension framework to creating an application, it connect to VPN server via NEVPNProtocolIKEv2.
After research, I found an tutorial about working with NetworkExtension framework, and I try to follow it.
(http://ramezanpour.net/post/2014/08/03/configure-and-manage-vpn-connections-programmatically-in-ios-8/)
But, I stuck when I configure identityData of this protocol.
Here is m code:
self.vpnManager.loadFromPreferencesWithCompletionHandler { [unowned self] (error) in
if error != nil {
printError("\(error?.errorDescription)")
return
}
let p = NEVPNProtocolIKEv2()
p.username = server.userName
p.serverAddress = server.serverUrl
// Get password persistent reference from keychain
self.createKeychainValue(server.password, forIdentifier: KeychainId_Password)
p.passwordReference = self.searchKeychainCopyMatching(KeychainId_Password)
p.authenticationMethod = NEVPNIKEAuthenticationMethod.None
self.createKeychainValue(kVPNsecret, forIdentifier: KeychainId_PSK)
p.sharedSecretReference = self.searchKeychainCopyMatching(KeychainId_PSK)
// certificate
p.identityData = ??????
p.useExtendedAuthentication = true
p.disconnectOnSleep = false
self.vpnManager.`protocol` = p
self.vpnManager.localizedDescription = server.serverName
self.vpnManager.saveToPreferencesWithCompletionHandler({ [unowned self] (error) in
if error != nil {
printError("Save config failed " + error!.localizedDescription)
}
})
}
In tutorial, p.identityData is NSData, that was loading from a P12 file.
But I have only a string that call: server.certificate
This server.certificate has a value like this
"-----BEGIN CERTIFICATE-----\nMIIEdDCCA1ygAwIBAgIBADANBgkqhki......1iEtCZg7SAlsBiaxpJzpZm5C6OifUCkUfZNdPQ==\n-----END CERTIFICATE-----\n"
This is a very very long string, that call x509Certificate... or something like that, I do not remember exactly.
I found an library support write an String to file p12, It is "openssl".
But demo code is Objective-C. I keep trying port this code to Swift, but it is so hard.
(democode: iOS: How to create PKCS12 (P12) keystore from private key and x509certificate in application programmatically?)
Finally, I have only a String certificate, and I want to configure p.identityData for my application.
How I do it?

Resources