如何在客户端和服务器之间实现iOS相互身份验证?

如何在客户端和服务器之间实现iOS相互身份验证?,ios,ssl-certificate,swift5,urlsession,urlauthenticationchallenges,Ios,Ssl Certificate,Swift5,Urlsession,Urlauthenticationchallenges,在我的一个应用程序中,我试图使用URLSession为我的iOS应用程序在客户端和服务器之间实现证书相互身份验证。我能够提取identityRef和trust和证书链,在didReceivechallenge方法中,我正在检查authenticationMethod并为URLSession的质询创建URLCredential 下面是我的代码 // Struct to save values of the Cert. struct IdentityAndTrust { var identit

在我的一个应用程序中,我试图使用URLSession为我的iOS应用程序在客户端和服务器之间实现证书相互身份验证。我能够提取identityReftrust和证书链,在didReceivechallenge方法中,我正在检查authenticationMethod并为URLSession的质询创建URLCredential

下面是我的代码

// Struct to save values of the Cert.

struct IdentityAndTrust {
  var identityRef: SecIdentity
  var trust: SecTrust
  var certificates: [SecCertificate]
 }

// Method to extract the identity, certificate chain and trust

func extractIdentity(certData: NSData, certPassword: String) -> IdentityAndTrust? {
  
  var identityAndTrust: IdentityAndTrust?
  var securityStatus: OSStatus = errSecSuccess
  
  var items: CFArray?
  let certOptions: Dictionary = [kSecImportExportPassphrase as String : certPassword]
  securityStatus = SecPKCS12Import(certData, certOptions as CFDictionary, &items)
  if securityStatus == errSecSuccess {
    let certificateItems: CFArray = items! as CFArray
    let certItemsArray: Array = certificateItems as Array
    let dict: AnyObject? = certItemsArray.first
    
    if let certificateDict: Dictionary = dict as? Dictionary<String, AnyObject> {
      
      // get the identity
      let identityPointer: AnyObject? = certificateDict["identity"]
      let secIdentityRef: SecIdentity = identityPointer as! SecIdentity
      
      // get the trust
      let trustPointer: AnyObject? = certificateDict["trust"]
      let trustRef: SecTrust = trustPointer as! SecTrust
      
      // get the certificate chain
      var certRef: SecCertificate? // <- write on
      SecIdentityCopyCertificate(secIdentityRef, &certRef)
      var certificateArray = [SecCertificate]()
      certificateArray.append(certRef! as SecCertificate)
      
      let count = SecTrustGetCertificateCount(trustRef)
      if count > 1 {
        for i in 1..<count {
          if let cert = SecTrustGetCertificateAtIndex(trustRef, i) {
            certificateArray.append(cert)
          }
        }
      }
      
      identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certificates: certificateArray)
    }
  }
  
  return identityAndTrust
}

// Delegate method of URLSession 

public class SessionDelegate : NSObject, URLSessionDelegate {
  
  public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    if let localCertPath = Bundle.main.url(forResource: "my_client", withExtension: "p12"),
       let localCertData = try?  Data(contentsOf: localCertPath)
    {
      
      let identityAndTrust:IdentityAndTrust = extractIdentity(certData: localCertData as NSData, certPassword: "eIwj5Lurs92xtC9B4CZ0")!
      
      if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate {
        
        let urlCredential:URLCredential = URLCredential(
          identity: identityAndTrust.identityRef,
          certificates: identityAndTrust.certificates as [AnyObject],
          persistence: URLCredential.Persistence.permanent);
        
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, urlCredential);
        
        return
      }
      if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
        let urlCredential:URLCredential = URLCredential(
          identity: identityAndTrust.identityRef,
          certificates: identityAndTrust.certificates as [AnyObject],
          persistence: URLCredential.Persistence.forSession);
        
        completionHandler(URLSession.AuthChallengeDisposition.useCredential, urlCredential);
//        completionHandler (URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!));
        return
      }
      completionHandler (URLSession.AuthChallengeDisposition.performDefaultHandling, Optional.none);
      return
    }
    
    challenge.sender?.cancel(challenge)
    completionHandler(URLSession.AuthChallengeDisposition.rejectProtectionSpace, nil)
  }
  
}
//保存证书值的结构。
结构标识和信任{
var identityRef:SecIdentity
var信托:部门信任
var证书:[SecCertificate]
}
//方法提取身份、证书链和信任
func extractIdentity(certData:NSData,certPassword:String)->IdentityAndTrust?{
var identityAndTrust:identityAndTrust?
var securityStatus:OSStatus=errSecSuccess
变量项:CFArray?
让certOptions:Dictionary=[ksecImportExportPassphrasString:certPassword]
securityStatus=SecPKCS12Import(certData、certOptions as CFDictionary和items)
如果securityStatus==errSecSuccess{
让certificateItems:CFArray=items!作为CFArray
让certItemsArray:Array=certificateItems作为数组
让dict:AnyObject?=certItemsArray.first
如果让CertificateDictionary=DictAs?Dictionary{
//获得身份
let identityPointer:AnyObject?=certificateDict[“identity”]
设secIdentityRef:SecIdentity=identityPointer为!SecIdentity
//得到信任
let trustPointer:AnyObject?=certificateDict[“信任”]
让trustRef:SecTrust=trustPointer为!SecTrust
//获取证书链
var certRef:SecCertificate?//1{
因为我在1.空虚中){
如果让localCertPath=Bundle.main.url(用于资源:“我的客户机”,扩展名为:“p12”),
让localCertData=try?数据(contentsOf:localCertPath)
{
让identityAndTrust:identityAndTrust=extractIdentity(certData:localCertData作为NSData,certPassword:“eIwj5Lurs92xtC9B4CZ0”)!
如果challenge.protectionSpace.authenticationMethod==nsurAuthenticationMethodClientCertificate{
让urlCredential:urlCredential=urlCredential(
标识:identityAndTrust.identityRef,
证书:identityAndTrust.certificates为[AnyObject],
持久性:URLCredential.persistence.permanent);
completionHandler(URLSession.AuthChallengeDisposition.useCredential,urlCredential);
返回
}
如果challenge.protectionSpace.authenticationMethod==nsurAuthenticationMethodServerTrust{
让urlCredential:urlCredential=urlCredential(
标识:identityAndTrust.identityRef,
证书:identityAndTrust.certificates为[AnyObject],
持久性:URLCredential.persistence.forSession);
completionHandler(URLSession.AuthChallengeDisposition.useCredential,urlCredential);
//completionHandler(URLSession.AuthChallengeDisposition.useCredential,URLCredential(信任:challenge.protectionSpace.serverTrust!));
返回
}
completionHandler(URLSession.AuthChallengeDisposition.performDefaultHandling,可选。无);
返回
}
质询。发送方?取消(质询)
completionHandler(URLSession.AuthChallengeDisposition.rejectProtectionSpace,无)
}
}
下面是我得到的答复

`Project XXXX[1115:755397] [tcp] tcp_output [C22.1:3] flags=[R.] seq=2988084600, 
 ack=2995213448, w`in=2047 state=CLOSED rcv_nxt=2995213448, snd_una=2988084600

Project XXXX[1115:755397] Connection 22: received failure notification
2021-05-18 12:39:08.000356+0530 Project XXXX[1115:755397] Connection 22: failed to connect 
3:-9816, reason -1
2021-05-18 12:39:08.000429+0530 Project XXXX[1115:755397] Connection 22: encountered 
error(3:-9816)

finished with error [-1200] Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://dev.xxxx.net/oauth/xxx/login, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <75A97E53-2AE1-4CE2-9C0D-64AA5965BCBC>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <75A97E53-2AE1-4CE2-9C0D-64AA5965BCBC>.<1>"
), NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://dev.projectzebra.net/oauth/zebra/login, NSUnderlyingError=0x282d26910 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=1, _kCFNetworkCFStreamSSLErrorOriginalValue=-9816, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9816}}, _kCFStreamErrorCodeKey=-9816}
`Project XXXX[1115:755397][tcp]tcp_输出[C22.1:3]标志=[R.]seq=2988084600,
ack=2995213448,w`in=2047状态=CLOSED rcv\u nxt=2995213448,snd\u una=2988084600
项目XXXX[1115:755397]连接22:收到故障通知
2021-05-18 12:39:08.000356+0530项目XXXX[1115:755397]连接22:连接失败
3:-9816,原因-1
2021-05-18 12:39:08.000429+0530项目XXXX[1115:755397]连接22:遇到
错误(3:-9816)
完成时出现错误[-1200]error Domain=NSURLErrorDomain Code=-1200“发生SSL错误,无法与服务器建立安全连接。”UserInfo={NSErrorFailingURLStringKey=https://dev.xxxx.net/oauth/xxx/login,nsLocalizedRecoverysSuggestion=是否仍要连接到服务器?,\u kCFStreamErrorDomainKey=3,\u nsurlerErrorFailingUrlSessionAskerWorkey=LocalDataTask.,\u nsurlerErrorRelatedUrlSessionTaskErrorKey=(
“LocalDataTask。”
),NSLocalizedDescription=发生SSL错误,无法与服务器建立安全连接。NSErrorFailingURLKey=https://dev.projectzebra.net/oauth/zebra/login,NSUnderlyingError=0x282d26910{Error Domain=kCFErrorDomainCFNetwork Code=-1200“(null)”用户信息={{kCFStreamPropertySSLClientCertificateState=1,{KCFnetworkCfStreamsLerrorOriginalValue=-9816,{kCFStreamErrorDomainKey=3,{kCFStreamErrorCodeKey=-9816},{kCFStreamErrorCodeKey=-9816}
我不确定我收到的回复,无法继续,如果有人遇到类似问题,请帮助


非常感谢您的帮助。谢谢。

我相信问题可能与以下代码有关:

if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust 
{
    let urlCredential:URLCredential = URLCredential(
       identity: identityAndTrust.identityRef,
       certificates: identityAndTrust.certificates as [AnyObject],
       persistence: URLCredential.Persistence.forSession);
        
    completionHandler(URLSession.AuthChallengeDisposition.useCredential, urlCredential);
    return
}
具体来说,您通过提供客户端证书来回答
nsurauthenticationmethodservertrust
挑战

我建议用以下代码替换该代码:

if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
{
    if let serverTrust: ServerTrust = challenge.protectionSpace.serverTrust
    {
        let credential: URLCredential = URLCredential(trust: serverTrust)
        completionHandler(.performDefaultHandling, credential)
        return
    }
}
您的服务器应配置为需要客户端证书,该证书将创建
nsurauthenticationmethodclientcertificate
质询…看来您正在处理此问题。确定

此外,web客户端需要信任服务器证书,这是
nsurAuthenticationMethodServerTrust
发挥作用的地方。默认处理将确保服务器证书根CA(证书颁发机构)位于iOS中的受信任机构列表中,证书未过期,等等

从您的问题中不清楚您是否打算同时检查服务器证书,例如,对于证书固定,此答案假设您没有

Pl
public class PKCS12 {
  var label:String?
  var keyID:NSData?
  var trust:SecTrust?
  var certChain:[SecTrust]?
  var identity:SecIdentity?

  public init(PKCS12Data:NSData,password:String)
  {
    let importPasswordOption:NSDictionary = [kSecImportExportPassphrase as NSString:password]
    var items : CFArray?
    let secError:OSStatus = SecPKCS12Import(PKCS12Data, importPasswordOption, &items)
    
    guard secError == errSecSuccess else {
      if secError == errSecAuthFailed {
        NSLog("ERROR: SecPKCS12Import returned errSecAuthFailed. Incorrect password?")
      }
      fatalError("SecPKCS12Import returned an error trying to import PKCS12 data")
    }
    
    guard let theItemsCFArray = items else { fatalError()  }
    let theItemsNSArray:NSArray = theItemsCFArray as NSArray
    guard let dictArray = theItemsNSArray as? [[String:AnyObject]] else { fatalError() }
    
    func f<T>(key:CFString) -> T? {
      for d in dictArray {
        if let v = d[key as String] as? T {
          return v
        }
        if(key == kSecImportItemLabel || key == kSecImportItemKeyID){
          var cert: SecCertificate?
          if let cd = d["identity"]{
            SecIdentityCopyCertificate(cd as! SecIdentity, &cert)
            if let certData = cert{
              if(key == kSecImportItemLabel){
                let lblDer = SecCertificateCopySubjectSummary(certData)
                if let lblVallue = lblDer {
                  return lblVallue as? T
                }
              }
              var key: SecKey?
              SecIdentityCopyPrivateKey(cd as! SecIdentity, &key)
              if let keyData = key{
                let keyDict = SecKeyCopyAttributes(keyData)
                if let keyDictUnwrapped = keyDict, let keyValue = (keyDictUnwrapped as NSDictionary)["v_Data"] as? NSData {
                  return keyValue as? T
                }
              }
              
            }
            
          }
          
        }
      }
      return nil
    }
    self.label = f(key: kSecImportItemLabel)
    self.keyID = f(key: kSecImportItemKeyID)
    self.trust = f(key: kSecImportItemTrust)
    self.certChain = f(key: kSecImportItemCertChain)
    self.identity =  f(key: kSecImportItemIdentity)
  }
}

extension URLCredential {
  public convenience init?(PKCS12 thePKCS12:PKCS12) {
    if let identity = thePKCS12.identity {
      self.init(
        identity: identity,
        certificates: thePKCS12.certChain,
        persistence: URLCredential.Persistence.forSession)
    }
    else { return nil }
  }
}