Reputation: 987
I am trying to figure out how to use Alamofire 4.0 with Swift 3.0 to send a p12 (i also have the PEM cert and key if need be) to a website for authentication. All the examples i have seen are for Swift 2.0 and not exactly what i'm looking for. In safari on my mac i can access the site by putting the p12 in the keychain and sending it when safari asks so i know that portion works. I don't know if anyone can help me with an example of how to do so in Alamofire 4.0 and Swift 3.0 in an application. The certificates are self signed as well.
Any thoughts or help? I am not just looking to pin the certificate as the client key and cert needs to be sent to the server for access...
Upvotes: 13
Views: 11589
Reputation: 562
Here is my example that might help someone (Alamofire 4.0, Swift 3, xCode 8)
import Alamofire
class NetworkConnection {
let developmentDomain = Config.developmentDomain // "api.myappdev.com"
let productionDomain = Config.productionDomain // "api.myappprod.com"
let certificateFilename = Config.certificateFilename // "godaddy"
let certificateExtension = Config.certificateExtension // "der"
let useSSL = true
var manager: SessionManager!
var serverTrustPolicies: [String : ServerTrustPolicy] = [String:ServerTrustPolicy]()
static let sharedManager = NetworkConnection()
init(){
if useSSL {
manager = initSafeManager()
} else {
manager = initUnsafeManager()
}
}
//USED FOR SITES WITH CERTIFICATE, OTHERWISE .DisableEvaluation
func initSafeManager() -> SessionManager {
setServerTrustPolicies()
manager = SessionManager(configuration: URLSessionConfiguration.default, delegate: SessionDelegate(), serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
return manager
}
//USED FOR SITES WITHOUT CERTIFICATE, DOESN'T CHECK FOR CERTIFICATE
func initUnsafeManager() -> SessionManager {
manager = Alamofire.SessionManager.default
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) //URLCredential(forTrust: challenge.protectionSpace.serverTrust!)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = self.manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
return manager
}
func setServerTrustPolicies() {
let pathToCert = Bundle.main.path(forResource: certificateFilename, ofType: certificateExtension)
let localCertificate:Data = try! Data(contentsOf: URL(fileURLWithPath: pathToCert!))
let serverTrustPolicies: [String: ServerTrustPolicy] = [
productionDomain: .pinCertificates(
certificates: [SecCertificateCreateWithData(nil, localCertificate as CFData)!],
validateCertificateChain: true,
validateHost: true
),
developmentDomain: .disableEvaluation
]
self.serverTrustPolicies = serverTrustPolicies
}
static func addAuthorizationHeader (_ token: String, tokenType: String) -> [String : String] {
let headers = [
"Authorization": tokenType + " " + token
]
return headers
}
}
add following to your Info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>api.myappdev.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSRequiresCertificateTransparency</key>
<false/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>
and here is an example of making an request
import Alamofire
class ActionUserUpdate {
let url = "https://api.myappdev.com/v1/"
let manager = NetworkConnection.sharedManager.manager
func updateUser(_ token: String, tokenType: String, expiresIn: Int, params: [String : String]) {
let headers = NetworkConnection.addAuthorizationHeader(token, tokenType: tokenType)
manager?.request(url, method: .put, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { response in
print(response.description)
print(response.debugDescription)
print(response.request) // original URL request
print(response.response) // URL response
print(response.data) // server data
print(response.result) // result of response serialization
}
}
}
Upvotes: 6
Reputation: 987
I was able to get it to work. A few issues got into the way. First, you have to allow IOS to accept self signed certificates. This requires to set up AlamoFire serverTrustPolicy:
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"your-domain.com": .disableEvaluation
]
self.sessionManager = Alamofire.SessionManager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
From there, you have to override the sessionDidRecieveChallenge to send the client certificate. Because i wanted to use a p12 file I modified some code I found elsewhere (sorry i don't have the source anymore) to make is Swift 3.0 to import the p12 using foundation classes:
import Foundation
public class PKCS12 {
var label:String?
var keyID:Data?
var trust:SecTrust?
var certChain:[SecTrust]?
var identity:SecIdentity?
let securityError:OSStatus
public init(data:Data, password:String) {
//self.securityError = errSecSuccess
var items:CFArray?
let certOptions:NSDictionary = [kSecImportExportPassphrase as NSString:password as NSString]
// import certificate to read its entries
self.securityError = SecPKCS12Import(data as NSData, certOptions, &items);
if securityError == errSecSuccess {
let certItems:Array = (items! as Array)
let dict:Dictionary<String, AnyObject> = certItems.first! as! Dictionary<String, AnyObject>;
self.label = dict[kSecImportItemLabel as String] as? String;
self.keyID = dict[kSecImportItemKeyID as String] as? Data;
self.trust = dict[kSecImportItemTrust as String] as! SecTrust?;
self.certChain = dict[kSecImportItemCertChain as String] as? Array<SecTrust>;
self.identity = dict[kSecImportItemIdentity as String] as! SecIdentity?;
}
}
public convenience init(mainBundleResource:String, resourceType:String, password:String) {
self.init(data: NSData(contentsOfFile: Bundle.main.path(forResource: mainBundleResource, ofType:resourceType)!)! as Data, password: password);
}
public func urlCredential() -> URLCredential {
return URLCredential(
identity: self.identity!,
certificates: self.certChain!,
persistence: URLCredential.Persistence.forSession);
}
}
This will allow me to import the file, and send it back to the client.
let cert = PKCS12.init(mainBundleResource: "cert", resourceType: "p12", password: "password");
self.sessionManager.delegate.sessionDidReceiveChallenge = { session, challenge in
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
return (URLSession.AuthChallengeDisposition.useCredential, self.cert.urlCredential());
}
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
return (URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!));
}
return (URLSession.AuthChallengeDisposition.performDefaultHandling, Optional.none);
}
Now you can use the sessionManager to create as many calls as you need to.
As a note, i've also added the following to the info.plist as recomended to get around the new security features in newer iOS features:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>your-domain.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
I hope this helps!
Upvotes: 24