简体   繁体   English

Swift SSL 自签名证书错误

[英]Swift SSL error with self signed certificate

This code attempts and fails to access an SSL URL which works in a browser:此代码尝试并未能访问在浏览器中工作的 SSL URL:

let path = "https://localhost:8443/greeting"
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()

let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
    let json:JSON = JSON(data: data!)
    if let c = json["content"].string {
        print(c)
    }
})
task.resume()

Fails with the error:失败并出现错误:

Optional(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=,可选(错误域=NSURLErrorDomain 代码=-1200“发生了 SSL 错误,无法与服务器建立安全连接。”UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=,

What is required to allow the app to accept this cert?允许应用程序接受此证书需要什么?

The certificate in question is self signed.有问题的证书是自签名的。 Read a few solutions on SO without success.阅读了一些关于 SO 的解决方案,但没有成功。

Running Xcode 7.2运行 Xcode 7.2

@Ashish Kakkad was spot on. @Ashish Kakkad 就在现场。 This works:这有效:

class Blah: NSURLSessionDelegate {

    func rest() {
        let path = "https://localhost:8443/greeting"
        let request = NSMutableURLRequest(URL: NSURL(string: path)!)
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
        let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
            let json:JSON = JSON(data: data!)
            if let c = json["content"].string {
                print(c)
            }
        })
        task.resume()
    }

    func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
        completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!))
    }
}

With this in the Info.plist file:在 Info.plist 文件中:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>localhost</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

Sai Reddy's solution allows you to accept your self-signed certificate if it has a complete chain, but it also accepts others. Sai Reddy 的解决方案允许您接受具有完整链的自签名证书,但它也接受其他证书。

Marcus Leon's solution is a complete override -- basically ignoring all certificates. Marcus Leon 的解决方案是完全覆盖——基本上忽略所有证书。

I like this one better.我更喜欢这个。

Swift 4.1, iOS 11.4.1斯威夫特 4.1,iOS 11.4.1

First, in your Info.plist:首先,在您的 Info.plist 中:

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <true/>
    </dict>

Second, wherever you use your NSURLSession, instead of setting up with URLSession.shared, use something like this:其次,无论您在哪里使用 NSURLSession,都不要使用 URLSession.shared 进行设置,而是使用以下内容:

session = URLSession(configuration: .default, delegate: APIURLSessionTaskDelegate(isSSLPinningEnabled: isSSLPinningEnabled), delegateQueue: nil)

Then add this class to handle pinning:然后添加这个类来处理固定:

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        print("*** received SESSION challenge...\(challenge)")
        let trust = challenge.protectionSpace.serverTrust!
        let credential = URLCredential(trust: trust)

        guard isSSLPinningEnabled else {
            print("*** SSL Pinning Disabled -- Using default handling.")
            completionHandler(.useCredential, credential)
            return
        }

        let myCertName = "my_certificate_to_pin"
        var remoteCertMatchesPinnedCert = false
        if let myCertPath = Bundle.main.path(forResource: myCertName, ofType: "der") {
            if let pinnedCertData = NSData(contentsOfFile: myCertPath) {
                // Compare certificate data
                let remoteCertData: NSData = SecCertificateCopyData(SecTrustGetCertificateAtIndex(trust, 0)!)
                if remoteCertData.isEqual(to: pinnedCertData as Data) {
                    print("*** CERTIFICATE DATA MATCHES")
                    remoteCertMatchesPinnedCert = true
                }
                else {
                    print("*** MISMATCH IN CERT DATA.... :(")
                }

            } else {
                print("*** Couldn't read pinning certificate data")
            }
        } else {
            print("*** Couldn't load pinning certificate!")
        }

        if remoteCertMatchesPinnedCert {
            print("*** TRUSTING CERTIFICATE")
            completionHandler(.useCredential, credential)
        } else {
            print("NOT TRUSTING CERTIFICATE")
            completionHandler(.rejectProtectionSpace, nil)
        }
    }
}

This class checks to see if you enabled certificate pinning.此类检查您是否启用了证书锁定。 If you did, it completely ignores the normal certificate validation and does an exact comparison with the cert we include in the app.如果你这样做了,它会完全忽略正常的证书验证,并与我们在应用程序中包含的证书进行精确比较。 In this way, it only accepts your self-signed cert, and nothing else.这样,它只接受你的自签名证书,不接受其他任何东西。

This solution requires that you put a " my_certificate_to_pin.der " file in your project, in your Resources folder.此解决方案要求您将“ my_certificate_to_pin.der ”文件放入项目的 Resources 文件夹中。 If you don't already have a Resources folder, just add one.如果您还没有 Resources 文件夹,只需添加一个。

That certificate should be in DER format.该证书应为 DER 格式。

To create a self-signed certificate for your server, you would normally do something like this:要为您的服务器创建自签名证书,您通常会执行以下操作:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout mycert.key -out mycert.cer

That generates two files -- a mycert.key private key file, and a mycert.cer -- the certificate itself.这会生成两个文件——一个mycert.key私钥文件和一个mycert.cer——证书本身。 These are both in the X509 format.它们都是 X509 格式。 For iOS, you will need the cert in DER format, so do this:对于 iOS,您将需要 DER 格式的证书,因此请执行以下操作:

openssl x509 -outform der -in mycert.cer -out my_certificate_to_pin.der

That generates the file you need on iOS.这会生成您在 iOS 上需要的文件。

you can use your own certificates instead of my certificates(fullchain.pem)你可以使用你自己的证书而不是我的证书(fullchain.pem)

    class AccessingServer: NSObject,URLSessionDelegate {

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            // First load our extra root-CAs to be trusted from the app bundle.
            let trust = challenge.protectionSpace.serverTrust

            let rootCa = "fullchain"
            if let rootCaPath = Bundle.main.path(forResource: rootCa, ofType: "pem") {
                if let rootCaData = NSData(contentsOfFile: rootCaPath) {

                    let rootCert = SecCertificateCreateWithData(nil, rootCaData)!

                    SecTrustSetAnchorCertificates(trust!, [rootCert] as CFArray)

                    SecTrustSetAnchorCertificatesOnly(trust!, false)
                }
            }

            var trustResult: SecTrustResultType = SecTrustResultType.invalid
            SecTrustEvaluate(trust!, &trustResult)

            if (trustResult == SecTrustResultType.unspecified ||
                trustResult == SecTrustResultType.proceed) {
                // Trust certificate.

                let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                challenge.sender?.use(credential, for: challenge)

            } else {
                NSLog("Invalid server certificate.")
                challenge.sender?.cancel(challenge)
            }
        } else {
            NSLog("Got unexpected authentication method \(challenge.protectionSpace.authenticationMethod)");
            challenge.sender?.cancel(challenge)
        }
    }

   }

Swift 5.1 Swift 5.1

I know you found the answer but i have an shorter way to this if anyone whant can use it我知道你找到了答案,但如果有人可以使用它,我有一个更短的方法

class AuthViewModel: NSObject, ObservableObject {

func login(username: String, password: String,completion: @escaping ((LoginModel) -> Void)) -> Void {
    
    let url = URL(string: "https://YourAPI.xyz/APP/?action=login&user=\(username)&pass=\(password)")!

    // Here is Important ****
    let req = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
    // *****

    let task = req.dataTask(with: request) { data1, response, error in
        guard
            let data = data1,
            let response = response as? HTTPURLResponse,
            error == nil
        else {                                                               // check for fundamental networking error
            print("error", error ?? URLError(.badServerResponse))
            return
        }
        
        guard (200 ... 299) ~= response.statusCode else {                    // check for http errors
            print("statusCode should be 2xx, but is \(response.statusCode)")
            print("response = \(response)")
            return
        }
        
        // do whatever you want with the `data`, e.g.:
        
        
        DispatchQueue.main.async {
            do {
                let resStreing = String(data: data, encoding: .utf8)
                let resData = resStreing?.data(using: .utf8)
                
                let responseObject = try JSONDecoder().decode(LoginModel.self, from: resData!)
                
                print("\n\n ResponseObject = \(responseObject) \n\n")
                completion(responseObject)
            } catch {
                print("CAN NOT PARSE!") // parsing error
                
                if let responseString = String(data: data, encoding: .utf8) {
                    print("responseString = \(responseString)")
                } else {
                    print("unable to parse response as string")
                }
            }
        }
    }

    task.resume()
    
    
}
}

The extension which you need:您需要的扩展:

extension AuthViewModel: URLSessionDelegate {
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
   //Trust the certificate even if not valid
   let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)

   completionHandler(.useCredential, urlCredential)
}
}

And usage:和用法:

authviewModel.login(username: "UserName",password: "Password") { loginModel in
// Do Somthing
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM