Getting client certificate to work for mutual authentication using Swift 3 and Alamofire 4

后端 未结 2 1068
孤街浪徒
孤街浪徒 2020-11-30 07:56

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 exam

相关标签:
2条回答
  • 2020-11-30 08:43

    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!

    0 讨论(0)
  • 2020-11-30 08:45

    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
                }
            }        
    }
    
    0 讨论(0)
提交回复
热议问题