如何实现iOS 客户端与服务端的相互认证?
How to implement iOS Mutual authentication between client and server?
在我的一个应用程序中,我正在尝试使用 URLSession 为我的 iOS 应用程序在客户端和服务器之间实现证书相互身份验证。我能够提取 identityRef 和 trust 和证书链以及我正在检查的 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)
}
}
以下是我收到的回复
`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}
我不确定收到的回复,无法继续进行,如果有人遇到类似问题,请提供帮助。
感谢任何帮助。谢谢
我认为问题可能与此代码有关:
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
}
具体来说,您是通过提供您的客户证书来回答 NSURLAuthenticationMethodServerTrust
挑战。
我建议用以下代码替换该代码:
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
{
if let serverTrust: ServerTrust = challenge.protectionSpace.serverTrust
{
let credential: URLCredential = URLCredential(trust: serverTrust)
completionHandler(.performDefaultHandling, credential)
return
}
}
您的服务器应配置为需要客户端证书,这将创建 NSURLAuthenticationMethodClientCertificate
挑战...看起来您正在处理这个问题。
此外,Web 客户端需要信任服务器证书,这就是 NSURLAuthenticationMethodServerTrust
发挥作用的地方。默认处理将确保服务器证书根 CA(证书颁发机构)在 iOS 中的受信任颁发机构列表中,证书未过期等
从你的问题中不清楚你是否打算也检查服务器证书,例如对于证书固定,此答案假定您不是。
请尝试以上方法并报告任何不同之处?
终于使用 .p12 文件并使用 PKCS12 方法从 .p12 中获取所有详细信息,如 identity、certChain、trust、keyID 并将这些内容分配给 URLCredentials对象并将该对象传递给发送者。
以下代码将帮助您实现客户端和服务器之间的 mTLS 身份验证。
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 }
}
}
在我的一个应用程序中,我正在尝试使用 URLSession 为我的 iOS 应用程序在客户端和服务器之间实现证书相互身份验证。我能够提取 identityRef 和 trust 和证书链以及我正在检查的 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)
}
}
以下是我收到的回复
`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}
我不确定收到的回复,无法继续进行,如果有人遇到类似问题,请提供帮助。
感谢任何帮助。谢谢
我认为问题可能与此代码有关:
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
}
具体来说,您是通过提供您的客户证书来回答 NSURLAuthenticationMethodServerTrust
挑战。
我建议用以下代码替换该代码:
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
{
if let serverTrust: ServerTrust = challenge.protectionSpace.serverTrust
{
let credential: URLCredential = URLCredential(trust: serverTrust)
completionHandler(.performDefaultHandling, credential)
return
}
}
您的服务器应配置为需要客户端证书,这将创建 NSURLAuthenticationMethodClientCertificate
挑战...看起来您正在处理这个问题。
此外,Web 客户端需要信任服务器证书,这就是 NSURLAuthenticationMethodServerTrust
发挥作用的地方。默认处理将确保服务器证书根 CA(证书颁发机构)在 iOS 中的受信任颁发机构列表中,证书未过期等
从你的问题中不清楚你是否打算也检查服务器证书,例如对于证书固定,此答案假定您不是。
请尝试以上方法并报告任何不同之处?
终于使用 .p12 文件并使用 PKCS12 方法从 .p12 中获取所有详细信息,如 identity、certChain、trust、keyID 并将这些内容分配给 URLCredentials对象并将该对象传递给发送者。
以下代码将帮助您实现客户端和服务器之间的 mTLS 身份验证。
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 }
}
}