Swift iOS Client Certificate Authentication

前端 未结 4 1237
迷失自我
迷失自我 2020-12-31 10:01

The web service I want to consume requires a client certificate. How can I send my certificate to it?

To further elaborate I don\'t understand how to create the

相关标签:
4条回答
  • 2020-12-31 10:07

    In order to respond to the authentication challenge you need to extract the identity from your client certificate.

    struct IdentityAndTrust {
    
        var identityRef:SecIdentityRef
        var trust:SecTrustRef
        var certArray:NSArray
    }
    
    func extractIdentity(certData:NSData, certPassword:String) -> IdentityAndTrust {
    
        var identityAndTrust:IdentityAndTrust!
        var securityError:OSStatus = errSecSuccess
    
        var items:Unmanaged<CFArray>?
        let certOptions:CFDictionary = [ kSecImportExportPassphrase.takeRetainedValue() as String: certPassword ];
    
        // import certificate to read its entries
        securityError = SecPKCS12Import(certData, certOptions, &items);
    
        if securityError == errSecSuccess {
    
            let certItems:CFArray = items?.takeUnretainedValue() 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!;
    
                // grab the trust
                let trustPointer:AnyObject? = certEntry["trust"];
                let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
    
                // grab the certificate chain
                var certRef:Unmanaged<SecCertificate>?
                SecIdentityCopyCertificate(secIdentityRef, &certRef);
                let certArray:NSMutableArray = NSMutableArray();
                certArray.addObject(certRef?.takeRetainedValue() as SecCertificateRef!);
    
                identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: certArray);
            }
        }
    
        return identityAndTrust;
    }
    

    In NSURLSessionDelegate respond to the authentication challenge like this:

    public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
    
        let bundle:NSBundle = NSBundle(forClass: self.dynamicType);
        let bundleCertPath:NSString = bundle.pathForResource("clientCertificateName", ofType: "p12")!;
        let certData:NSData = NSData(contentsOfFile: bundleCertPath as String)!;
        let identityAndTrust:IdentityAndTrust = self.certificateHelper.extractIdentity(certData, certPassword: "C00lp@assword");
    
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
    
            let urlCredential:NSURLCredential = NSURLCredential(
                    identity: identityAndTrust.identityRef,
                    certificates: identityAndTrust.certArray as [AnyObject],
                    persistence: NSURLCredentialPersistence.ForSession);
    
            completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, urlCredential);
    
    
        } else {
    
            // nothing here but us chickens
        }
    }
    
    0 讨论(0)
  • 2020-12-31 10:20

    I am using last xcode and swift version, and this code work for me, using a client certificate .pfx, based on Bins Ich answer:

     func extractIdentity(certData:NSData) -> IdentityAndTrust { 
        var identityAndTrust:IdentityAndTrust!
        var securityError:OSStatus = errSecSuccess
        var items:Unmanaged<CFArray>?
        let certOptions:CFDictionary = [ kSecImportExportPassphrase.takeRetainedValue() as String: "password" ];
    
        // import certificate to read its entries
        securityError = SecPKCS12Import(certData, certOptions, &items);
    
        if securityError == errSecSuccess {
            let certItems:CFArray = items?.takeUnretainedValue() 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!;
    
                // grab the trust
                let trustPointer:AnyObject? = certEntry["trust"];
                let trustRef:SecTrustRef = trustPointer as! SecTrustRef;
    
                // grab the cert
                let chainPointer:AnyObject? = certEntry["chain"];
                let chainRef:SecCertificateRef = chainPointer as! SecCertificateRef;
                let  certArray:CFArrayRef = chainRef as! CFArrayRef
    
                identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray:  certArray);
            }
        }
        return identityAndTrust;
    }
    
    func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
    
        let strTemp = challenge.protectionSpace.authenticationMethod
    
        if(strTemp == NSURLAuthenticationMethodServerTrust) {
             challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
        }
    
        if(strTemp == NSURLAuthenticationMethodClientCertificate) {
    
            let certFile = NSBundle.mainBundle().pathForResource("mycert", ofType:"pfx")
    
            let p12Data = NSData(contentsOfFile:certFile!)
            let identityAndTrust:IdentityAndTrust = extractIdentity(p12Data!)
    
            let urlCredential:NSURLCredential = NSURLCredential(
                identity: identityAndTrust.identityRef,
                certificates:identityAndTrust.certArray as [AnyObject],
                persistence: NSURLCredentialPersistence.Permanent)
    
            challenge.sender.useCredential(urlCredential ,forAuthenticationChallenge:challenge)
        }
    }
    
    0 讨论(0)
  • 2020-12-31 10:26

    Find in this Gist the Swift3 working implementation :

    https://gist.github.com/celian-m/8da09ad293507940a0081507f057def5

    0 讨论(0)
  • 2020-12-31 10:28

    Technically, when someone I know needed the implementation in Swift, he used the following Objective-C implementation in order to get the NSURLCredential object to the connection; based on the private key and X509 Certificate pair contained in a PKCS12 keystore.

    Sorry, I don't have access to the source with the Swift solution. All I know is that the NSURLCredential was returned to Swift, and used directly in the http url connection there. It's similar to this one, though.

    I'm not an iOS dev so I won't be able to help you out with the "bridging to Swift" part.

    - (void)getMessageWithURL:(NSString *)url {
    
        NSURL *URL = [NSURL URLWithString:url];
    
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
        [request setURL:URL];
        [request setHTTPMethod:@"GET"];
        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        [connection self];
    }
    
    - (void)postMessageWithURL:(NSString *)url withContent:(NSString *)content {
    
        NSData *postData = [content dataUsingEncoding:NSUTF8StringEncoding];
        NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
    
        NSURL *myURL = [NSURL URLWithString:url];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:myURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60];
    
        [request setHTTPMethod:@"POST"];
        [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        [request setHTTPBody:postData];
    
        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        [connection self];
    
    }
    
    - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
        return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
        NSLog(@"didReceiveAuthenticationChallenge");
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        responseData = [[NSMutableData alloc] init];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [responseData appendData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        NSLog(@"Unable to fetch data");
        NSLog(@"%@", error);
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        NSLog(@"Succeeded! Received %lu bytes of data", (unsigned long)[responseData
                length]);
    
        NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        NSLog(@"%@", responseString);
    
        [bridge callHandler:handlerName data:responseString];
    
    }
    
    - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    
        /*
        Reading the certificate and creating the identity
        */
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = paths[0]; // Get documents directory
    
        NSData *p12data = [CertificateManager getP12Data]; //returns essentially a byte array containing a valid PKCS12 certificate
    
        if (!p12data) {
          return;
          NSAssert(p12data, @"Couldn't load p12 file...");
        }
    
        CFStringRef password = CFSTR("password");
    
        const void *keys[] = {kSecImportExportPassphrase};
        const void *values[] = {password};
        CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        CFArrayRef p12Items;
    
        OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);
    
        if (result == noErr) {
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
            SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    
            SecCertificateRef certRef;
            SecIdentityCopyCertificate(identityApp, &certRef);
    
            SecCertificateRef certArray[1] = {certRef};
            CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
            CFRelease(certRef);
    
            NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
            CFRelease(myCerts);
    
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        }
        else {
            // Certificate is invalid or password is invalid given the certificate
            NSLog(@"Invalid certificate or password");
            NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
            return;
        }
    }
    

    EDIT: Har har, very funny, downvoting me twice when you yourself didn't bother while the bounty was up. *grumble *

    Anyways, to use the following above, you just need to access it from Swift.

    func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
        if let p12Data = UserManager.currentP12,
           let credential = CertificateManager.getCredentialsForP12(p12Data) as? NSURLCredential {
                challenge.sender.useCredential(credential, forAuthenticationChallenge: challenge)
        } else {
            UIApplication.sharedApplication().networkActivityIndicatorVisible = false
        }   
    }
    

    That uses this.

    + (id)getCredentialsForP12:(NSData *)p12 {
        NSData* p12data = p12;
        const void *keys[] = {kSecImportExportPassphrase};
        const void *values[] = {CFSTR("thePassword")};
        CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
        CFArrayRef p12Items;
        OSStatus result = SecPKCS12Import((__bridge CFDataRef) p12data, optionsDictionary, &p12Items);
        if (result == noErr) {
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
            SecIdentityRef identityApp = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
            SecCertificateRef certRef;
            SecIdentityCopyCertificate(identityApp, &certRef);
            SecCertificateRef certArray[1] = {certRef};
            CFArrayRef myCerts = CFArrayCreate(NULL, (void *) certArray, 1, NULL);
            CFRelease(certRef);
    
            NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:nil persistence:NSURLCredentialPersistenceNone];
            CFRelease(myCerts);
            return credential;
    
        }
        else {
            // Certificate is invalid or password is invalid given the certificate
            NSLog(@"Invalid certificate or password");
    
            UIAlertView* av = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Invalid cert or pass" delegate:nil cancelButtonTitle:@"ok" otherButtonTitles: nil];
            [av show];
            NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo:nil];
            return nil;
        }
    

    EDIT: A swift version of the above is here, although it was messy enough that we rather just didn't use it.

                var p12items : Unmanaged<CFArrayRef>?
    
                let index: CFIndex = 1
                let password: CFString = "password"
                let key = kSecImportExportPassphrase.takeRetainedValue() as String
                var values = [unsafeAddressOf(password)]
                var keys = [unsafeAddressOf(key)]
    
                var keyCallbacks = kCFTypeDictionaryKeyCallBacks
                var valueCallbacks = kCFTypeDictionaryValueCallBacks
    
                let length: CFIndex = p12Data.length
                let p12CfData: CFData = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(p12Data.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 key = kSecImportItemIdentity.takeRetainedValue() as String
                    var keyAddress = unsafeAddressOf(key)
                    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 credential: NSURLCredential = NSURLCredential(identity: identityApp, certificates: [AnyObject](), persistence: NSURLCredentialPersistence.None)
    
    0 讨论(0)
提交回复
热议问题