使用Swift 3和Alamofire 4获取客户端证书以进行相互身份验证

时间:2016-10-11 20:09:04

标签: ios ssl swift3 alamofire

我正在试图弄清楚如何使用Alamofire 4.0和Swift 3.0将p12(我还有PEM证书和密钥,如果需要的话)发送到网站进行身份验证。我见过的所有例子都是针对Swift 2.0而不是我正在寻找的。在我的Mac上的safari中,我可以通过将p12放入钥匙串并在safari询问时发送它来访问该网站,所以我知道该部分有效。我不知道是否有人可以帮助我在一个应用程序中的Alamofire 4.0和Swift 3.0中如何这样做。证书也是自签名的。

有任何想法或帮助吗?我不只是想将证书固定为客户端密钥,并且需要将证书发送到服务器以进行访问...

2 个答案:

答案 0 :(得分:17)

我能够让它发挥作用。一些问题陷入了困境。首先,您必须允许IOS接受自签名证书。这需要设置AlamoFire serverTrustPolicy:

let serverTrustPolicies: [String: ServerTrustPolicy] = [
        "your-domain.com": .disableEvaluation
    ]

self.sessionManager = Alamofire.SessionManager(
        serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
    )

从那里,您必须覆盖sessionDidRecieveChallenge以发送客户端证书。因为我想使用p12文件,我修改了一些我在其他地方找到的代码(抱歉,我没有源代码)使用Swift 3.0来导入p12使用基础类:

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);

    }



}

这将允许我导入文件,并将其发送回客户端。

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);
    }

现在,您可以使用sessionManager根据需要创建任意数量的呼叫。

作为一个说明,我还在info.plist中添加了以下内容,以推荐更新iOS功能中的新安全功能:

<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>

我希望这有帮助!

答案 1 :(得分:6)

这是我可以帮助某人的例子(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
    }

}

将以下内容添加到您的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>

这是一个发出请求的例子

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
            }
        }        
}