Client certificate authentication in swift2

2019-04-13 14:48发布

问题:

I want to authenticate client certificate in my app. I am getting the following errors in didReceiveChallenge function. I found this solution in this link.

My didReceiveChallenge func code:

   func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {

    if challenge.protectionSpace.authenticationMethod == (NSURLAuthenticationMethodServerTrust) {


    let serverTrust:SecTrustRef = challenge.protectionSpace.serverTrust!
    let certificate: SecCertificateRef = SecTrustGetCertificateAtIndex(serverTrust, 0)!
    let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
    let cerPath: String = NSBundle.mainBundle().pathForResource("example.com", ofType: "cer")!
    let localCertificateData = NSData(contentsOfFile:cerPath)!


    if (remoteCertificateData.isEqualToData(localCertificateData) == true) {
        let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust)
        challenge.sender?.useCredential(credential, forAuthenticationChallenge: challenge)
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))

    } else {

        completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil)
    }
    }

    else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate
    {

        let path: String = NSBundle.mainBundle().pathForResource("client", ofType: "p12")!
        let PKCS12Data = NSData(contentsOfFile:path)!

        var p12items : Unmanaged<CFArray>?

        let index: CFIndex = 1
        let password: CFString = "password"

        var values = [unsafeAddressOf(password)]
        var keys = [unsafeAddressOf(kSecImportExportPassphrase)]

        var keyCallbacks = kCFTypeDictionaryKeyCallBacks
        var valueCallbacks = kCFTypeDictionaryValueCallBacks

        let length: CFIndex = PKCS12Data.length
        let p12CfData: CFData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(PKCS12Data.bytes), length)

        let options = CFDictionaryCreate(kCFAllocatorDefault, &keys, &values, index, &keyCallbacks, &valueCallbacks)
        let result = SecPKCS12Import(p12CfData, options, p12items)

        if result == noErr {

            let idIndex: CFIndex = 0
            var items = p12items?.takeRetainedValue()
            var identityDict = CFArrayGetValueAtIndex(items!, idIndex)

            var keyAddress = unsafeAddressOf(kSecImportItemIdentity)
            var identityApp: SecIdentityRef = CFDictionaryGetValue(identityDict, keyAddress)
            var certRef : Unmanaged<SecCertificateRef>?
            SecIdentityCopyCertificate(identityApp, certRef)

            var cert: SecCertificateRef = certRef!.takeRetainedValue()
            var certArray = [unsafeAddressOf(cert)]
            var arrayCallback = kCFTypeArrayCallBacks
            var myCerts: CFArrayRef = CFArrayCreate(kCFAllocatorDefault, &certArray, index, &arrayCallback);


        let urlCredential:NSURLCredential = NSURLCredential(
            identity: identityApp,
            certificates: myCerts as [AnyObject],
            persistence: NSURLCredentialPersistence.Permanent)

        challenge.sender!.useCredential(urlCredential ,forAuthenticationChallenge:challenge)

    }


}

Any example for client authentication in NSURLSession will be helpful. Thanks in advance.

回答1:

Following are the code changes which works fine with Self Signed SSL certificate

  func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {

    if challenge.protectionSpace.authenticationMethod == (NSURLAuthenticationMethodServerTrust) {


    let serverTrust:SecTrustRef = challenge.protectionSpace.serverTrust!
    let certificate: SecCertificateRef = SecTrustGetCertificateAtIndex(serverTrust, 0)!
    let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
    let cerPath: String = NSBundle.mainBundle().pathForResource("xyz.com", ofType: "cer")!
    let localCertificateData = NSData(contentsOfFile:cerPath)!


        if (remoteCertificateData.isEqualToData(localCertificateData) == true) {
            let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust)

            challenge.sender?.useCredential(credential, forAuthenticationChallenge: challenge)


            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))

        } else {

            completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil)
        }
    }
    else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate
    {

        let path: String = NSBundle.mainBundle().pathForResource("client", ofType: "p12")!
        let PKCS12Data = NSData(contentsOfFile:path)!


        let identityAndTrust:IdentityAndTrust = self.extractIdentity(PKCS12Data);



            let urlCredential:NSURLCredential = NSURLCredential(
                identity: identityAndTrust.identityRef,
                certificates: identityAndTrust.certArray as? [AnyObject],
                persistence: NSURLCredentialPersistence.ForSession);

            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, urlCredential);




    }
    else
    {
        completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge, nil);
    }
}

 struct IdentityAndTrust {

    var identityRef:SecIdentityRef
    var trust:SecTrustRef
    var certArray:AnyObject
}

func extractIdentity(certData:NSData) -> IdentityAndTrust {
    var identityAndTrust:IdentityAndTrust!
    var securityError:OSStatus = errSecSuccess

    let path: String = NSBundle.mainBundle().pathForResource("client", ofType: "p12")!
    let PKCS12Data = NSData(contentsOfFile:path)!
    let key : NSString = kSecImportExportPassphrase as NSString
    let options : NSDictionary = [key : "xyz"]
    //create variable for holding security information
    //var privateKeyRef: SecKeyRef? = nil

    var items : CFArray?

     securityError = SecPKCS12Import(PKCS12Data, options, &items)

    if securityError == errSecSuccess {
        let certItems:CFArray = items as CFArray!;
        let certItemsArray:Array = certItems as Array
        let dict:AnyObject? = certItemsArray.first;
        if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> {

            // grab the identity
            let identityPointer:AnyObject? = certEntry["identity"];
            let secIdentityRef:SecIdentityRef = identityPointer as! SecIdentityRef!;
            print("\(identityPointer)  :::: \(secIdentityRef)")
            // grab the trust
            let trustPointer:AnyObject? = certEntry["trust"];
            let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
            print("\(trustPointer)  :::: \(trustRef)")
            // grab the cert
            let chainPointer:AnyObject? = certEntry["chain"];
            identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray:  chainPointer!);
        }
    }
    return identityAndTrust;
}

Changes done in the info.plist file

     <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>amazonaws.com.cn</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSThirdPartyExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
        </dict>
        <key>amazonaws.com</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSThirdPartyExceptionMinimumTLSVersion</key>
            <string>TLSv1.0</string>
        </dict>
        <key>xyz.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSTemporaryExceptionMinimumTLSVersion</key>
            <string>TLSv1.2</string>
            <key>NSRequiresCertificateTransparency</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>
</plist>


回答2:

For someone who interested, this is the small modification from the code of @Karlos, which is compatible with Swift 3.0:

fileprivate func extractCredential(_ certData: Data) -> Credential? {
    // REF.: itx | EB418667-6ECD-4D22-B31E-0BB4000849A2
    var credential: Credential!

    let key = kSecImportExportPassphrase as NSString

    let importPasswordOption:NSDictionary = [key as NSString:cerPassword]
    var items: CFArray?
    let securityError = SecPKCS12Import(certData as CFData, importPasswordOption, &items)

    if securityError == errSecSuccess {
        let certItems = items as CFArray!
        let certItemsArray = (certItems as! NSArray)
        let dict = certItemsArray.first

        guard let certEntry = certItemsArray.firstObject as? [String:AnyObject] else { fatalError() }

        // Identity
        let identityPointer = certEntry["identity"];
        let secIdentityRef = identityPointer as! SecIdentity!;

        // Trust
        let trustPointer = certEntry["trust"];
        let trustRef = trustPointer as! SecTrust;

        // Certificates
        let chainPointer = certEntry["chain"];

        credential = Credential(identityRef: secIdentityRef!, trust: trustRef, certArray:  chainPointer!);
    }

    return credential;
}