【问题标题】:Swift iOS Client Certificate AuthenticationSwift iOS 客户端证书认证
【发布时间】:2015-05-11 17:33:38
【问题描述】:

我要使用的 Web 服务需要客户端证书。如何将我的证书发送给它?

为了进一步详细说明,我不明白如何创建SecIdentityRef

在我的NSURLConnectiondidReceiveAuthenticationChallengeServerTrust 之后有这个条件:

else if challenge?.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate
    {
        var secIdent : SecIdentityRef = ?????????
        var certCred = NSURLCredential(identity: secIdent, certificates: [getClientCertificate()], persistence: NSURLCredentialPersistence.Permanent)
        challenge?.sender.useCredential(certCred, forAuthenticationChallenge: challenge!)
    }

getClientCertificate 方法:

func getClientCertificate() -> SecCertificateRef
{
    let mainBundle : NSBundle = NSBundle.mainBundle()
    var mainBund = mainBundle.pathForResource("iosClientCert", ofType: "cer") //exported the cert in der format.
    var key : NSData = NSData(contentsOfFile: mainBund!)!
    var turnToCert : SecCertificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, key).takeRetainedValue()

    return turnToCert;
}

【问题讨论】:

  • 感谢@EpicPandaForce 的赏金
  • 我们遇到了同样的问题,但我们仍在努力解决它... --- 我们可能必须直接移植以下解决方案 stackoverflow.com/a/27012819/2413303 才能访问不过,斯威夫特。
  • 我必须让应用程序进入测试阶段,所以我不得不将其切换为使用令牌等的身份验证。我会尽快重新审视这个。我几乎走了桥接头路由并在 obj-c 中实现了 http 客户端......但我也没有时间。
  • “来自 EpicPandaForce 的声誉在 22 小时内结束”等等
  • .cer 是一个没有任何密钥的简单导出。我们将如何在请求期间附加它。有人解决了这个问题吗?

标签: ios swift authentication ssl client-certificates


【解决方案1】:

从技术上讲,当我认识的某个人需要 Swift 中的实现时,他使用以下 Objective-C 实现来获取连接的 NSURLCredential 对象; based on the private key and X509 Certificate pair contained in a PKCS12 keystore.

抱歉,我无法使用 Swift 解决方案访问源代码。我所知道的是 NSURLCredential 已返回给 Swift,并直接用于那里的 http url 连接。不过,它与此类似。

我不是 iOS 开发人员,因此无法在“桥接到 Swift”部分为您提供帮助。

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

编辑:哈哈,非常有趣,当你自己在赏金结束时没有打扰我时两次投票给我。 *抱怨*

无论如何,要使用上面的内容,你只需要从 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
    }   
}

使用这个。

+ (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;
    }

编辑:上面有一个 swift 版本,虽然它很混乱,我们宁愿不使用它。

            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)

【讨论】:

  • 有趣。我想这有点离题,但为什么需要包括整个公共和私人?谢谢您的回答。这周我会尝试一下,看看能否在 Swift 中实现。谢谢公积金局。
  • 对不起 Obj-C 代码,我真的无法访问 Swift 桥接。您在这里所说的“公共和私人”是指哪一部分?
  • 我的意思是密钥必须是 PKCS,这意味着它必须包含证书的公钥和私钥。在其他语言中,我已经能够使用 .cer 文件,该文件只是没有私钥的直接导出,并且不需要证书密码。没什么大不了的,因为我们有 CA 签署的证书,但对我来说有点奇怪,就是这样。
  • 从技术上讲,这就是它在 Java 中的实现方式,这里的一位朋友设法将我用 BouncyCastle 所做的整个事情映射到在 iOS 上使用 OpenSSL。但是为了使客户端身份验证工作,您确实需要一个私钥和一个公钥;因为你用收件人的公钥加密东西,他们用你的公钥加密;但是要解密它,您需要您的私钥,该私钥存储在密钥库中。这是在 SecPKCS12Import 调用中导入的,以使两者都可用于 SSL 连接。客户端身份验证很神奇,奇怪的是它在任何地方都没有。
  • 所以我访问了 Swift 代码,并添加了其中的一部分。我不会删除我的答案,只是因为一些对客户端身份验证一无所知的人觉得在这里闯入并表现得非常聪明并投反对票。
【解决方案2】:

在此 Gist 中查找 Swift3 工作实现:

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

【讨论】:

    【解决方案3】:

    为了响应身份验证质询,您需要从客户端证书中提取身份。

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

    NSURLSessionDelegate 中像这样响应身份验证质询:

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

    【讨论】:

    • 这很有帮助,但我收到了一个钥匙串警报,要求用户允许访问私钥。没想到,是不是我做错了什么?
    • 谢谢,但你有没有机会提供一个关于如何使用它的例子?
    【解决方案4】:

    我正在使用最新的 xcode 和 swift 版本,此代码适用于我,使用客户端证书 .pfx,基于 Bins Ich 答案:

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

    【讨论】:

    • 谢谢何塞!你碰巧知道是什么改变了这项工作吗?它与我尝试使其工作时使用的一些第一个代码非常相似。
    • 我使用 let chainPointer:AnyObject 获得证书链? = certEntry["链"];让 chainRef:SecCertificateRef = chainPointer as! SecCertificateRef;让 certArray:CFArrayRef = chainRef as! CFArrayRef
    猜你喜欢
    • 1970-01-01
    • 2015-09-06
    • 2010-11-30
    • 2012-09-04
    • 2013-08-04
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    相关资源
    最近更新 更多