ios nsurlauthenticationmethodclientcertificate与ActiveSync服务器



我正在尝试在我正在开发的ActiveSync客户端中实现证书身份验证。使用证书验证的代码可能会起作用,但是目前服务器或更准确地说,iOS库对服务器响应的解释对我来说似乎是不正确的。这是我的代码:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
    NSString *authenticationMethod = [protectionSpace authenticationMethod];
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate])
    {
        NSURLCredential* credential = [ self buildCredentialClientCert];
        if ( credential == nil )
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        }
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        .... // do other stuff

问题是,即使我知道服务器支持客户端证书auth,当我设置一个断点时,则始终将authenticationMethod设置为NSURLAuthenticationMethodServerTrust

RAW HTTPS服务器响应包含以下内容:

错误代码:403禁止。该页面需要客户证书作为身份验证过程的一部分。如果您使用的是智能卡,则需要插入智能卡以选择合适的证书。否则,请联系您的服务器管理员。(12213)

我的问题是,什么决定身份验证挑战是否是NSURLAuthenticationMethodServerTrustNSURLAuthenticationMethodClientCertificate

,因为没有人回答这个问题,我确实达到了一个工作解决方案,这里是。

服务器信任不是服务器对客户端的挑战,它是客户端验证服务器提供的信任的机会。考虑到这一点,下面的代码无法验证该信任,但可以。

通常,您会得到nsurlauthenticationmethodservertrust,然后随后获得nsurlauthenticationmethodclientcertificate。这不是或一个。这是工作代码。

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
    NSString *authenticationMethod = [protectionSpace authenticationMethod];
    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate] && self.accountCertKeychainRef != nil)
    {
        SecIdentityRef identity = [KeychainUtilities retrieveIdentityWithPersistentRef:self.accountCertKeychainRef];
        NSURLCredential* credential = [CertificateUtilities getCredentialFromCert:identity];
        if ( credential == nil )
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        }
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM] || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic])
    {
        self.lastProtSpace = [challenge protectionSpace];
        if ([challenge previousFailureCount] > 2)
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender]  useCredential:[self buildCredential] forAuthenticationChallenge:challenge];
        }
    }
    else
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}

对于以下问题,您可以如何获得身份:

+ (SecIdentityRef)copyIdentityAndTrustWithCertData:(CFDataRef)inPKCS12Data password:(CFStringRef)keyPassword
{
    SecIdentityRef extractedIdentity = nil;
    OSStatus securityError = errSecSuccess;
    const void *keys[] = {kSecImportExportPassphrase};
    const void *values[] = {keyPassword};
    CFDictionaryRef optionsDictionary = NULL;
    optionsDictionary = CFDictionaryCreate(NULL, keys, values, (keyPassword ? 1 : 0), NULL, NULL);
    CFArrayRef items = NULL;
    securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);
    if (securityError == errSecSuccess) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
        // get identity from dictionary
        extractedIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
        CFRetain(extractedIdentity);
    }
    if (optionsDictionary) {
        CFRelease(optionsDictionary);
    }
    if (items) {
        CFRelease(items);
    }
    return extractedIdentity;
}

对于那些有兴趣的人,这里是GetCredentialForcert:

+ (NSURLCredential *)getCredentialFromCert:(SecIdentityRef)identity
{
    SecCertificateRef certificateRef = NULL;
    SecIdentityCopyCertificate(identity, &certificateRef);
    NSArray *certificateArray = [[NSArray alloc] initWithObjects:(__bridge_transfer id)(certificateRef), nil];
    NSURLCredentialPersistence persistence = NSURLCredentialPersistenceForSession;
    NSURLCredential *credential = [[NSURLCredential alloc] initWithIdentity:identity
                                                               certificates:certificateArray
                                                                persistence:persistence];
    return credential;
}

发布相应的Swift 5版本。如果您的服务器正在验证客户端证书,则行为是两个挑战:第一个NSURLAuthenticationMethodServerTrust,然后是NSURLAuthenticationMethodClientCertificate

import Foundation
struct DiskCredsProvider{
    let clientCert: URL = Bundle.main.url(forResource: "client", withExtension: "crt")!
    let clientKey: URL = Bundle.main.url(forResource: "client", withExtension: "key")!
    let pkcs12: URL = Bundle.main.url(forResource: "cert", withExtension: "p12")!
    let pkcs12Password: String = "xxx"
    
    func keyData() throws -> Data{
        return try Data.init(contentsOf: clientKey)
    }
    
    
    func certData() throws -> Data{
        return try Data.init(contentsOf: clientCert)
    }
    
    
    func p12Data() throws -> Data{
        return try Data.init(contentsOf: pkcs12)
    }
    
    
    /// Provides URL Session Auth Chanllenge disposition by loading on disk pkcs12 file
    /// - Returns: tuple with URLSession disposition and URLCredential
    func provideUrlSessionDispostionWithPKCS12Data(data: Data) -> (URLSession.AuthChallengeDisposition, URLCredential){
        //You can also import a PKCS #12 file directly into your app using the certificate, key, and trust services API.
        let pcks12Data:CFData = data as CFData
        
        
        // CKTS API won’t even import PKCS #12 data that lacks a password. Password during import, you create an options dictionary with the password string:
        let password = pkcs12Password
        let options = [ kSecImportExportPassphrase as String: password ]
        
        //Because the PKCS #12 format allows for bundling multiple cryptographic objects together, this function populates an array object. In our case, it's the cert&key, we create a c type array to store it.
        var rawItems: CFArray?
        let status = SecPKCS12Import(pcks12Data, options as CFDictionary, &rawItems)
        
        //Check SecPCKCS12 import status code.
        
        precondition(status == errSecSuccess)
        let items = rawItems! as! Array<Dictionary<String, Any>>
        
        precondition(items.count == 1)
        let firstItem = items[0]
        
        let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
        
        
        let disposition: URLSession.AuthChallengeDisposition = .useCredential
        
        // In most cases the server does not need any intermediate certificates in order to evaluate trust on your client certificate (it either has a fixed list of client certificates, or requires that the client certificate be issued by a specific issuer, in which case it already has any intermediates leading to that issuer), and thus you don’t need to include them.
        let creds = URLCredential(identity: identity!, certificates: nil, persistence: .forSession)
                
        return (disposition,creds)
        
    }
}
enum APIError: Error {
    case pcks12ImportFailed
    case serverError
}
let endPointUrl: URL = URL(string: "https://(endPointHost)/xx/xx/conf")!
let endPointHost: String = "xxxxx"

class WGAPIHelper:NSObject{
    
    var session: URLSession!
    
    let credsProvider: DiskCredsProvider = DiskCredsProvider()
    
    
    override init() {
        super.init()
        session = URLSession(configuration: .default, delegate: self, delegateQueue: queue)
    }
    
    lazy var queue: OperationQueue = {
        let oq = OperationQueue()
        oq.name = Bundle.main.bundleIdentifier!
        return oq
    }()
    
    
    
    func requestWGConfig(completionHandler: @escaping (String?,Error?) -> Void){
        
        let dataTask = session.dataTask(with: endPointUrl) { data, response, error in
            
            // check for fundamental networking error
            guard let data = data,
                  let response = response as? HTTPURLResponse,
                  error == nil else {
                      print("request error: (error?.localizedDescription ?? "unknown error")")
                      completionHandler(nil,APIError.serverError)
                      return
                  }
            
            // check for http errors
            guard (200 ... 299) ~= response.statusCode else {
                print("request statusCode should be 2xx, but is (response.statusCode)")
                print("response = (response)")
                completionHandler(nil,APIError.serverError)
                return
            }
            
            completionHandler(String(data: data, encoding: .utf8), nil)
        }
        dataTask.resume()
    }
    
}
//MARK: - URLSessionDelegate
extension WGAPIHelper: URLSessionDelegate{
    
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        switch (challenge.protectionSpace.authenticationMethod,challenge.protectionSpace.host) {
        case (NSURLAuthenticationMethodServerTrust, endPointHost):
            
            
            let servTrust = challenge.protectionSpace.serverTrust!
            let credential = URLCredential(trust: servTrust)
            //By Default trust it. if you are using self sign server cert, overide trust settings here.
            
            completionHandler(.useCredential, credential)
            return
            
        
        case (NSURLAuthenticationMethodClientCertificate, endPointHost):
            let data = try! credsProvider.p12Data()
            let (disposition, creds) = credsProvider.provideUrlSessionDispostionWithPKCS12Data(data: data)
            completionHandler(disposition, creds)
            return
            
        default:
            completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling, nil)
        }
    }
}

相关内容

  • 没有找到相关文章

最新更新