我正在尝试在我正在开发的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)
我的问题是,什么决定身份验证挑战是否是NSURLAuthenticationMethodServerTrust
对NSURLAuthenticationMethodClientCertificate
?
,因为没有人回答这个问题,我确实达到了一个工作解决方案,这里是。
服务器信任不是服务器对客户端的挑战,它是客户端验证服务器提供的信任的机会。考虑到这一点,下面的代码无法验证该信任,但可以。
通常,您会得到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)
}
}
}