How to do SSL public key pinning in Alamofire swift 5 - ios

I am trying to integrate SSL public key pinning in Alamofire swift 5, but I found ServerTrustPolicyManager which is deprecated. Please help me to integrate. Thanks.

To integrate SSL public key pinning you first have to add your SSL certificate in your project's target by dragging and dropping it.
To test if your certificate is in the correct format you can try to get the value from publicKeys parameter of the AlamofireExtension in your main Bundle, like this:
print("Bundle public keys: \(Bundle.main.af.publicKeys)")
If that array have at least one element, then you are ready. If it does not, then try importing your SSL certificate to your Mac's Keychain, then export it as .cer and then add it to your project's target. (this should work)
To check if the public key of the SSL certificate is the one that you import in your project you can use the Alamofire's ServerTrustManager with a PublicKeysTrustEvaluator instance, when you create your Session:
let evaluators: [String: ServerTrustEvaluating] = [
"your.domain.com": PublicKeysTrustEvaluator()
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: serverTrustManager)
Make sure that in the evaluators dictionary, the key ("your.domain.com" in the code above) is your servers domain and if you don't want for Alamofire to perform the default validation and/or validate the host you can pass false to those parameters in PublicKeysTrustEvaluator's initializer:
let evaluators: [String: ServerTrustEvaluating] = [
"your.domain.com": PublicKeysTrustEvaluator(
performDefaultValidation: false,
validateHost: false
)
]
let serverTrustManager = ServerTrustManager(evaluators: evaluators)
let session = Session(serverTrustManager: serverTrustManager)
Then you have to use this Session instance to make any request in your domain, like this:
let url = "https://your.domain.com/path/to/api"
session.request(url, method: .post, parameters: parameters).responseDecodable { response in
}
As #JonShier pointed out in the comments: You need to keep your Session alive beyond the declaring scope. Usually this is done through a single or other outside reference.

Related

Alamofire certificate pinning not working

I'm trying to implement certificate pinning using Alamofire.
I have added mydomain to "Exception Domains" in .plist file and set keys:
NSExceptionAllowsInsecureHTTPLoads: true
NSIncludesSubdomains: true
NSExceptionRequiresForwardSecrecy: false
Then, I create SessionManager this way:
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"mydomain": .pinCertificates(certificates: [],
validateCertificateChain: true,
validateHost: true)
]
let configuration = URLSessionConfiguration.default
sessionManager = SessionManager(configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
When I run my app, all requests succeed, but the expected result is Alamofire rejecting them. I've set breakpoints to all "didReceiveChallenge" methods and they're not even executed.
When I change the configuration to
let configuration = URLSessionConfiguration.background(withIdentifier: "background")
Then cert pinning works as expected (rejecting all requests)
Anyone has an idea why it happens and how to fix it using default configuration?
Note: When I pass my .der file to certificates in .pinCertificates policy, background configuration also works as expected
I've finally found a reason - Wormholy. I had to remove this library to make cert pinning working

Swift Keychain doesn't load externally generated keys. SecKey returns Nil value after loading key without errors

I am new to SWIFT, iOS as and Cryptography. Please bear with me. I am unable to load external keys in the iOS keychain in Swift 5. I came across several posts in Stackoverflow and other sites about the difficulty in loading keys into an iOS keychain.
My goal is to establish an SSH connection to a remote SFTP server using port 22, the public key and user name credentials. As of now, a firewall rule allows connection to the server using NMSSH (from GIT), but I am unable to authenticate without public keys exchanged between i-phone and Windows server. I generated key pairs from my Swift project several times and sent public key to Windows server admin to add it to my user name in SSH setup. All attempts by the admin failed with server lockouts. So finally he generated a key pair in the server using puTTYGen, added the public key to my user name and sent the key pair to me to add to an iOS keychain.
I exported the private key to OpenSSL in puTTYgen and converted it to base64 encoded string before using it in RSAUtils in GIT (https://github.com/btnguyen2k/swiftutils). The process works but returns SecKey as Nil. Looks like the key never made it to the keychain.
Before using this GIT code, I tried several times to use secItemAdd. The code works and shows like taking the key, but returns Nil as SecKey value in SecItemCopyMatching.
I also ventured into adding Objective-C in Swift by creating a .h header, a .m file and a bridge for the .m code in http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/. Same issue. I get a Nil after the above .m completes and returns a value.
The private key I am dealing with has header and footer -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY-----. I can see characters like +, / etc in the key suggesting that it is not base64, so I used a String extension to convert it.
func toBase64() -> String {
return Data(self.utf8).base64EncodedString()
The code:
// added to use GIT code
// https://github.com/btnguyen2k/swiftutils.git
// variable declared at class level
var pKeyContents: String = ""
func addKeys2() {
let tag: String = "com.mail.private"
// if let path = Bundle.main.path(forResource: "PKeyWOHeader", ofType: "txt") // I tried this first after manually removing header and footer ("BEGIN ....KEY AND "END ... KEY" etc). It didn't work
if let path = Bundle.main.path(forResource: "ExportAsOpenSSL", ofType: "") // This one is a dump of puTTYgen export to openssl of private key
{
do {
let contents = try String(contentsOfFile: path)
pKeyContents = contents
}
catch {
print("Contents could not be loaded")
return
}
} else {
print("File not found")
return
}
let myData = pKeyContents.data(using: String.Encoding.utf8)
let encoded = pKeyContents.toBase64()
//let instanceOfObjCKeyFix = ObjCKeyFix()
//privateKeyAsData = instanceOfObjCKeyFix.stripPublicKeyHeader(myData)
// stringFromData = String(decoding: privateKeyAsData!, as: UTF8.self)
// Above lines were an attempt to use Objective C code from
// http://blog.flirble.org/2011/01/05/rsa-public-key-openssl-ios/.
// This was also returning nil value
do {
try privateKey = RSAUtils.addRSAPrivateKey(encoded, tagName: tag)
print(privateKey as Any) // returns nil
} catch {
print("error)")
}
}
My goal is to load keys from outside to iOS keychain. I also tried sending public key created by using CryptoExportImportManager() suggested in https://digitalleaves.com/blog/2015/10/sharing-public-keys-between-ios-and-the-rest-of-the-world/. Like I said before, my Windows server admin reports server locks while attempting to load such keys.
Any help is appreciated.
Thanks
I am updating this with my own answer. After several weeks of trying two solutions referred in above links (Digital Leaves and Flirble.org), I could get connected and authenticated to a Windows server using NMSSH from an I-phone 7. I couldn't use I-phone keychain to store externally generated keys. It was too tedious. The best I could get to was add a private key to keychain, but it always returned Nil when I tried to retrieve it.
I followed connection steps recommended in NMSSH, used cocopod, added to framework and added libraries. While using NMSSH sessions class, I used the method with in memory keys option. The important point is to leave public key parameter blank and provide private key and password for private key.

Swift-NIO secured websocket server

I am trying to create websocket server and client in my iOS app, which i successfully managed to do with the help of sample implementation here. (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) - so current working situation is, i run the websocket server when app launches and then I load the client in a webview which can connect to it.
Now my problem is I want my server to secured websocket server (Basically connect to the websocket server from a HTTPS html page)
I am new to network programming and Swift-nio documentation is lacking to say the least. As far as I understand I could use (https://github.com/apple/swift-nio-transport-services)
I found this thread which is exactly what I need - https://github.com/apple/swift-nio-transport-services/issues/39 - I could disable the TLS authentication as I dont care in my usecase as long as I could get the websocket connected.
So my question is how to I extend my client (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketClient) and server (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) to use swift-nio-transport-service.
I could add the NIOSSLContext and stuff but I think I need to add the EventLoopGroup and new bootstrap methods. I know the answers is right there.... but I just cannot seem to pinpoint it.
Any pointer would be appreciated.
Thanks.
To translate a simple NIO Server to a NIOTransportServices one, you need to make the following changes:
Add a dependency on NIOTransportServices to your server.
Change MultiThreadedEventLoopGroup to NIOTSEventLoopGroup.
Change ClientBootstrap to NIOTSConnectionBootstrap.
Change ServerBootstrap to NIOTSListenerBootstrap.
Build and run your code.
Some ChannelOptions don’t work in NIOTransportServices, but most do: the easiest way to confirm that things are behaving properly is to quickly test the common flow.
This doesn’t add any extra functionality to your application, but it does give you the same functionality using the iOS APIs.
To add TLS to either NIOTSConnectionBootstrap or NIOTSListenerBootstrap, you use the .tlsOptions function. For example:
NIOTSListenerBootstrap(group: group)
.tlsOptions(myTLSOptions())
Configuring a NWProtocolTLS.Options is a somewhat tricky thing to do. You need to obtain a SecIdentity, which requires interacting with the keychain. Quinn has discussed this somewhat here.
Once you have a SecIdentity, you can use it like so:
func myTLSOptions() -> NWProtocolTLS.Options {
let options = NWProtocolTLS.Options()
let yourSecIdentity = // you have to implement something here
sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(yourSecIdentity)
return options
}
Once you have that code written, everything should go smoothly!
As an extension, if you wanted to secure a NIO server on Linux, you can do so using swift-nio-ssl. This has separate configuration as the keychain APIs are not available, and so you do a lot more loading of keys and certificates from files.
I needed a secure websocket without using SecIdentity or NIOTransportServices, so based on #Lukasa's hint about swift-nio-ssl I cobbled together an example that appears to work correctly.
I dunno if it's correct, but I'm putting it here in case someone else can benefit. Error-handling and aborting when the try's fail is left out for brevity.
let configuration = TLSConfiguration.forServer(certificateChain: try! NIOSSLCertificate.fromPEMFile("/path/to/your/tlsCert.pem").map { .certificate($0) }, privateKey: .file("/path/to/your/tlsKey.pem"))
let sslContext = try! NIOSSLContext(configuration: configuration)
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
ws.onText { ws, string in
print("Received text: \(string)")
}
ws.onBinary { ws, buffer in
// We don't accept any Binary data
}
ws.onClose.whenSuccess { value in
print("onClose")
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
_ = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let handler = NIOSSLServerHandler(context: sslContext)
_ = channel.pipeline.addHandler(handler)
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
try! server.close(mode: .all).wait()

create iOS 12 NWConnection that uses client cert

I'm trying to set up an NWConnection that does client side certs:
self.connection = NWConnection(
host: NWEndpoint.Host("servername"),
port: NWEndpoint.Port(integerLiteral: 8899),
using: .tls)
But I think that simple .tls class var needs to be a much more involved NWParameters object, but I'm at a complete loss (documentation is pretty sparse) as to what I create there to attach the client certs to the parameters. Nor do I know how I even move from .crt/.pem file to something the app manages programatically.
What is an example of how one would configure the NWParameters to support the client certs?
Context
I'm trying to set up a client connection to communicate with an MQTT broker using client side certificates. I've been able to proof-of-concept this all on the Linux side using command line. The MQTT broker is set to require client cert, and a command like:
mosquitto_pub -h servername -p 8899 -t 1234/2/Q/8 -m myMessage --cafile myChain.crt --cert client.crt --key client.pem
does the job nicely. But OpenSSL is enough a black box (to me) on iOS that I don't know where to go from here. I have been able to get all of the other MQTT communications work with my NWConnection instances, including server side TLS and even if it's self signed.
The kind folks on the Apple Developer Forums helped work this out. On iOS you have to use the p12 import ability:
let importOptions = [ kSecImportExportPassphrase as String: "" ]
var rawItems: CFArray?
let status = SecPKCS12Import(P12Data as CFData, importOptions as CFDictionary, &rawItems)
let items = rawItems! as! Array<Dictionary<String, Any>>
let firstItem = items[0]
let clientIdentity = firstItem[kSecImportItemIdentity as String]! as! SecIdentity
print("clientIdentity \(clientIdentity)")
Now that one has an identity, you can use that to configure the securityProtocolOptions of the the TLS options:
let options = NWProtocolTLS.Options()
sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(clientIdentity)!)
sec_protocol_options_set_challenge_block(options.securityProtocolOptions, { (_, completionHandler) in
completionHandler(sec_identity_create(clientIdentity)!)
}, .main)
let parameters = NWParameters(tls: options) // use this in the NWConnection creation
For reference, the Apple Developer Forum topic where this is discussed.

How to globally set custom headers in Swift app?

So I have an app that makes frequent requests to various endpoints on our API, and every request pretty much has the same custom headers sent with it. I'd like to know if there is a way to globally set custom header using NSURLSessionConfiguration, and if so...what is the syntax in Swift and where would I put it? AppDelegate? I've done some searching and can't seem to find a good example of this. Is it a bad practice? Not doable?
EDIT:
I'm using Alamofire for request/response, so I need something that sets them globally so that that library (and others that happen to use NSURLSession) will send the headers along by default.
We have this documented right in the README.
var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
let manager = Alamofire.Manager(configuration: configuration)
Then you need to use the new manager instead of the global Alamofire singleton.
manager.request(.GET, "https://httpbin.org/get")
.responseJSON { _, _, result in
debugPrint(result)
}
This will attach the DNT header to every request that is sent through this manager instance.
Each Manager instance has its own internal NSURLSession which also has its own configuration. Therefore, this override only works for this Manager instance. If you need these headers on a different Manager instance, you'll have to set it up the same way.

Resources