I trying to make a https call to my web service from a ios application.
So I used:
//...
func sendMessage() {
let defaults = UserDefaults.standard
let url = URL(string: defaults.string(forKey:"host")! + ":" + defaults.string(forKey:"port")! + "/garage")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: String] = [
"identifier": defaults.string(forKey: "secret")!
]
request.httpBody = parameters.percentEncoded()
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
let task = session.dataTask(with: request) { data, response, error in
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else { // check for fundamental networking error
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else { // check for http errors
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
let responseString = String(data: data, encoding: .utf8)
debugPrint("responseString = \(responseString)")
}
task.resume()
}
//...
extension ViewController: 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)
}
}
I installed the ca at on the target system too. But I also try to ignore the certificate.
I always get following error instead of a response:
2020-07-03 14:41:27.742361+0200 GarageOpener[5177:223905] ATS failed system trust
2020-07-03 14:41:27.742484+0200 GarageOpener[5177:223905] Connection 1: system TLS Trust evaluation failed(-9802)
2020-07-03 14:41:27.742642+0200 GarageOpener[5177:223905] Connection 1: TLS Trust encountered error 3:-9802
2020-07-03 14:41:27.742776+0200 GarageOpener[5177:223905] Connection 1: encountered error(3:-9802)
2020-07-03 14:41:27.746709+0200 GarageOpener[5177:223905] Task <63EC553D-485C-478D-813B-AF0C1D3D3223>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9802])
2020-07-03 14:41:27.749778+0200 GarageOpener[5177:223905] Task <63EC553D-485C-478D-813B-AF0C1D3D3223>.<1> 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={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x600002b187e0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7fd6f2035600) s: *.mydomain.de i: *.mydomain.de>"
), NSUnderlyingError=0x600001773cf0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x600002b187e0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7fd6f2035600) s: *.mydomain.de i: *.mydomain.de>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://subdomain.mydomain.de:443/garage, NSErrorFailingURLStringKey=https://subdomain.mydomain.de:443/garage, NSErrorClientCertificateStateKey=0}
"3"
error Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x600002b187e0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7fd6f2035600) s: *.mydomain.de i: *.mydomain.de>"
), NSUnderlyingError=0x600001773cf0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x600002b187e0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7fd6f2035600) s: *.mydomain.de i: *.mydomain.de>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://subdomain.mydomain.de:443/garage, NSErrorFailingURLStringKey=https://subdomain.mydomain.de:443/garage, NSErrorClientCertificateStateKey=0}
I know ignore isn't a good idea and it would allow man in the middle attacks. But at least the connection would be encrypted. And if somebody has the ability to make a man in the middle attack in my WLAN I have bigger problems than this.
Please help ;)
Okay, maybe it's time to answer my question.
The first I did wrong was the signing of the certificate.
I just set the domain/ip-address of the server in the Common Name (CN) entry of the certificate.
That is an outdated solution. You need to set the subjectAltName for modern applications: http://wiki.cacert.org/FAQ/subjectAltName .
But it feels very ugly to use the instructions from that wiki site to self-sign a certificate. So I used the awesome tool certstrap: https://github.com/square/certstrap
After that:
I passed the server.key and server.crt (private and signed public
key) to the server and configured everything fine.
I downloaded the CA (ca.crt) that I created (on the iphone).
You need to go to the settings->general->profiles and trust your
certificate.
Finally, you need to trust the certificate again:
https://support.apple.com/en-us/HT204477#:~:text=If%20you%20want%20to%20turn,Mobile%20Device%20Management%20(MDM).
Done. My application worked!
Related
What's Expected:
Response with status code 200 and a JSON body with auth token etc.
What's Happening:
A device will (randomly?) no longer make the login call to auth0 successfully.
HTTP status code, response data will all be nil values.
-Other devices running the same app branch, same wifi network are able to make the call with no issue. Then after some time, generally 1-2 days the problem phone will no longer have any trouble making the call and will continue on as before the issue started.
Throws error Code 303 (CFNetworkErrors.cfErrorHTTPParseFailure) in logs. Apple docs say it's a failure to parse the response from the server. I believe it's because the response data is null.
App Version:
This issue has shown up so far on iOS devices running 14.6 or higher.
Testing:
Used both an AlamoFire based and URLSession based network call and both can have the issue. The example code below is using the URLSession instead of the AlamoFire, but switching does not seem to have an effect on stopping devices from being able to develop the issue.
Changing from WiFi to Celluar internet has no effect, changing to another WiFi network has no effect either.
Deleting the App and reinstalling has no effect.
Because I have not figured out what triggers the issue I have only got my hands on a device with the issue a few times before it resolved itself and there was nothing to test.
I have managed to save some logs (print statements and some Firebase Analytics console messages). The segments I think are useful are pasted below.
Network call Code:
func makeNetworkCall(url: String, method: HTTPMethod = .get, parameters: Parameters? = nil, headers: HTTPHeaders? = nil, timeout: Double, completion: #escaping (DefaultURLRequestReturnModel) -> Void ) {
logger.debug("makingCall with: url= _\(url)_ method= _\(method)_, param= _\(parameters)_, headers= _\(headers)_")
var urlRequest: URLRequest?
var hasReturned: Bool = false
DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: {
if !hasReturned {
completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.timeoutError.rawValue, action: .failure, dataResponse: nil))
}
})
do {
urlRequest = try URLRequest(url: url, method: method, headers: headers)
if let parameters = parameters {
urlRequest?.httpBody = try JSONSerialization.data(withJSONObject: parameters)
}
} catch {
completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.encodeError.rawValue, action: .failure, dataResponse: nil))
return
}
// Unwrap the optional urlRequest to confirm its != nil
guard var safeURLRequest = urlRequest else {
completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.encodeError.rawValue, action: .failure, dataResponse: nil))
return
}
if safeURLRequest.httpBody != nil {
safeURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
let session = URLSession.shared
logger.debug("before session.dataTask for url \(safeURLRequest.url)")
let task = session.dataTask(with: safeURLRequest, completionHandler: { data, response, error in
logger.debug("url session completion for safeURLRequest.url _\(safeURLRequest.url)_, response= _\(response)_, error= _\(error.debugDescription)_, data= _\(data)_")
if let httpResponse = response as? HTTPURLResponse {
logger.debug("url session completion for URL \(safeURLRequest.url): status \(httpResponse.statusCode)")
switch httpResponse.statusCode {
case 200, 204:
let defaultDataResponse = DefaultDataResponse(request: safeURLRequest, response: httpResponse, data: data, error: error)
completion(DefaultURLRequestReturnModel(isSuccess: true, statusCode: httpResponse.statusCode, action: httpResponse.statusCode == 200 ? .success : .successEmpty, dataResponse: defaultDataResponse))
default:
logger.debug("ON ERROR")
completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: httpResponse.statusCode, action: httpResponse.statusCode == 401 ? .authError : .failure, dataResponse: nil))
}
} else {
// This is Trigged in the error logs
logger.debug("url session completion for URL \(safeURLRequest.url): response = \(response) __ can't be cast to HTTPURLResponse")
if NetworkManager.isConnectedToInternet() { logger.networkNilEvent() }
completion(DefaultURLRequestReturnModel(isSuccess: false, statusCode: ErrorCodes.noResponseError.rawValue, action: .failure, dataResponse: nil))
}
hasReturned = true
})
task.resume()
}
Console Logs From A Problem Device:
debug: url session completion for safeURLRequest.url
Optional(https://cpht.auth0.com/oauth/ro), response= nil, error= _Optional(Error Domain=kCFErrorDomainCFNetwork Code=303 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask
.<9>,
_kCFStreamErrorDomainKey=4, NSErrorPeerAddressKey=<CFData 0x28375a080 [0x1f9151860]>{length = 16, capacity = 16, bytes =
0x100201bb6810b8f80000000000000000}, _kCFStreamErrorCodeKey=-2205,
NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask .<9>" )}), data= nil debug: url session completion for URL
Optional(https://cpht.auth0.com/oauth/ro): response = nil __ can't be
cast to HTTPURLResponse
Firebase Analytics Logs From A Problem Device:
I have replaced the IP Addresses from something like 100.0.0.0:443 to IP:443 in the following logs.
App name has been changed to MyAppName
2021-08-12 16:59:51.615734-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.615828-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.624933-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.625051-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.625570-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.625716-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_read_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed
2021-08-12 16:59:51.625773-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_write_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed
2021-08-12 16:59:51.625828-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_keepalive_handler [C14 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for keepalive failed
2021-08-12 16:59:51.626028-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.626859-0500 MyAppName[4586:1910198] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 10909f7c8
2021-08-12 16:59:51.627061-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_read_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed
2021-08-12 16:59:51.627118-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_adaptive_write_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed
2021-08-12 16:59:51.627257-0500 MyAppName[4586:1910198] [connection] nw_endpoint_handler_set_keepalive_handler [C13.2.1 IP:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for keepalive failed
2021-08-12 16:59:51.641425-0500 MyAppName[4586:1909925] [] nw_protocol_instance_access_flow_state [C13.2.1:2] Failed to find flow 1090aae88
2021-08-12 16:59:51.647736-0500 MyAppName[4586:1909925] Task .<9> HTTP load failed, 529/0 bytes (error code: 303 [4:-2205])
2021-08-12 16:59:51.648181-0500 MyAppName[4586:1909923] Task .<9> finished with error [303] Error Domain=kCFErrorDomainCFNetwork Code=303 "(null)" UserInfo={_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask .<9>, _kCFStreamErrorDomainKey=4, NSErrorPeerAddressKey=<CFData 0x28375a080 [0x1f9151860]>{length = 16, capacity = 16, bytes = 0x100201bb6810b8f80000000000000000}, _kCFStreamErrorCodeKey=-2205, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask .<9>"
)}
2021-08-12 16:59:51.648326-0500 MyAppName[4586:1909925] [connection] nw_endpoint_handler_unregister_context [C13.2.1 IP:443 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] Cannot unregister after flow table is released
2021-08-12 16:59:51.648393-0500 MyAppName[4586:1909925] [h3connection] 0x1099d1a18 13 closed with peer error 258
2021-08-12 16:59:51.648500-0500 MyAppName[4586:1909925] [connection] nw_endpoint_handler_add_write_request [C13.2.1 IP:443 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] Cannot send after flow table is released
2021-08-12 16:59:51.648625-0500 MyAppName[4586:1909925] [connection] nw_write_request_report [C13] Send failed with error "Socket is not connected"
Thank you for any of your ideas on fixing this issue!
We had the same issuie that you described. In our case, one of our custom request headers exceeded the max header size limit of the webserver. Nginx has a max header size around 8kb and will return a 413 error to the client if you exceed this limit. for whatever reason, the ios app will not parse this response and throw a 303 error. After we increased the http2_max_header_size to 32kb, it worked.
I've developed an app in SwiftUI where I also use a chat with Parse LiveQuery.
The chat is working perfectly in real time until the app goes in background or the phone screen is closed.
If I receive a notification for a new message and I open the app from the notification then the LiveQuery stopes working and I recieve the error : "Parse LiveQuery: Error processing message: Optional(POSIXErrorCode: Software caused connection abort)"
I register the Message class
class Message: PFObject, PFSubclassing {
#NSManaged var message : String
#NSManaged var receiver : PFObject
#NSManaged var thread_id : PFObject
#NSManaged var produs : PFObject
#NSManaged var sender : PFObject
#NSManaged var picture : PFObject
class func parseClassName() -> String {
return "Message"
}
}
On the ChatView I have .onReceive where I start the function for chat and liveQuery
var messagesQuery: PFQuery<Message> {
return (Message.query()?
.whereKey("thread_id", equalTo: self.mainThread_obj)
.order(byDescending: "createdAt")) as! PFQuery<Message>
}
self.StartMessagesLiveQuery(messagesQuery: messagesQuery)
The func StartMessagesLiveQuery is:
let subscription: Subscription<Message> = Client.shared.subscribe(messagesQuery)
subscription.handle(Event.created) { query, object in
print("Live Query Trigered")
print(object)
DispatchQueue.global(qos: .background).async {
///reading object and appending to array}}
The only problem is when the app is opened from Notification.
I've there any way to force ParseLiveQuery to reconnect?
Error stack:
2021-03-03 10:15:47.442021+0200 App[35496:6350370] [connection] nw_read_request_report [C2] Receive failed with error "Software caused connection abort"
2021-03-03 10:15:47.449044+0200 App[35496:6350370] [connection] nw_read_request_report [C3] Receive failed with error "Software caused connection abort"
2021-03-03 10:15:47.449291+0200 App[35496:6350370] [connection] nw_read_request_report [C1] Receive failed with error "Software caused connection abort"
2021-03-03 10:15:47.455008+0200 App[35496:6350370] [connection] nw_flow_add_write_request [C2.1 52.1.38.170:443 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] cannot accept write requests
2021-03-03 10:15:47.455055+0200 App[35496:6350370] [connection] nw_write_request_report [C2] Send failed with error "Socket is not connected"
2021-03-03 10:15:47.455815+0200 App[35496:6350657] ParseLiveQuery: Error processing message: Optional(POSIXErrorCode: Software caused connection abort)
2021-03-03 10:15:47.456612+0200 App[35496:6350370] Task <F6A87098-D5D3-444B-B656-D1999D5089D1>.<44> HTTP load failed, 42/0 bytes (error code: -1005 [1:53])
2021-03-03 10:15:47.462120+0200 App[35496:6350657] Task <F6A87098-D5D3-444B-B656-D1999D5089D1>.<44> finished with error [-1005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={_kCFStreamErrorCodeKey=53, NSUnderlyingError=0x282d11830 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x280174aa0 [0x20a814660]>{length = 16, capacity = 16, bytes = 0x100201bb344692570000000000000000}, _kCFStreamErrorCodeKey=53, _kCFStreamErrorDomainKey=1}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <F6A87098-D5D3-444B-B656-D1999D5089D1>.<44>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <F6A87098-D5D3-444B-B656-D1999D5089D1>.<44>"
), NSLocalizedDescription=The network connection was lost., NSErrorFailingURLStringKey=https://site.b4a.app/classes/_User, NSErrorFailingURLKey=https://site.b4a.app/classes/_User, _kCFStreamErrorDomainKey=1}
2021-03-03 10:15:47.462237+0200 App[35496:6350657] [Error]: The network connection was lost. (Code: 100, Version: 1.19.1)
2021-03-03 10:15:47.462335+0200 Bazar[35496:6350657] [Error]: Network connection failed. Making attempt 1 after sleeping for 1.588128 seconds.
After this the network connection is restored and the app is functional, only the LiveQuery are not working.
I think i’ve solved it.
I've modified function StartMessagesLiveQuery:
func StartMessagesLiveQuery(messagesQuery: PFQuery<Message>){}
Inside the function I had:
let subscription: Subscription<Message> = Client.shared.subscribe(messagesQuery)
subscription!.handle(Event.created) { query, object in ... }
Now i’ve modified and put inside the Class
var subscription: Subscription<Message>?
var subscriber: ParseLiveQuery.Client!
and in the function i’ve modified with:
subscriber = ParseLiveQuery.Client()
subscription = subscriber.subscribe(messagesQuery)
Here is the curl command that works:
curl -d {"Key1":"value1"} -k -vvvv --request POST --header "Content-Type: application/json" --key KEY.pem --cacert CRT.pem --cert KEY.pem "URL"
How do I translate this to Alamofire request command? I get the authentication failure errors:
Connection 1: default TLS Trust evaluation failed(-9807)
2020-04-09 01:51:46.604692-0600 CertificatePinningExample[7192:1891639] Connection 1: TLS Trust encountered error 3:-9807
2020-04-09 01:51:46.604879-0600 CertificatePinningExample[7192:1891639] Connection 1: encountered error(3:-9807)
2020-04-09 01:51:46.606672-0600 CertificatePinningExample[7192:1891639] Connection 1: unable to determine interface type without an established connection
2020-04-09 01:51:46.650936-0600 CertificatePinningExample[7192:1891639] Task <9E539D4B-9694-426E-B382-6350044743B0>.<1> HTTP load failed, 0/0 bytes (error code: -1202 [3:-9807])
2020-04-09 01:51:46.662507-0600 CertificatePinningExample[7192:1891652] Task <9E539D4B-9694-426E-B382-6350044743B0>.<1> finished with error [-1202] Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “BLAH” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
// Alamofire code to fire up the request
override func viewDidLoad() {
super.viewDidLoad()
let evaluators = [
"SERVER_IP":
PinnedCertificatesTrustEvaluator(certificates:
getCertificates()
)
]
let session = Session(
serverTrustManager: ServerTrustManager(evaluators: evaluators)
)
let url = URL(string: "URL_TO_HIT")!
//enableCertificatePinning()
var dict: NSDictionary = ["SessionId":""]
var data: Data = Data.init()
do {
data = try JSONSerialization.data(withJSONObject: dict, options: [])
} catch{
data = "".data(using: .utf8)!
}
//let data = try JSONSerialization.data(withJSONObject: dict, options: [])
let request = AF.request(url, method: .post)
// 2
request.responseJSON { (data) in
print(data)
}
}
private func getCertificates() -> [SecCertificate] {
let url = Bundle.main.url(forResource: "ExampleCert", withExtension: "der")!
let localCertificate = try! Data(contentsOf: url) as CFData
guard let certificate = SecCertificateCreateWithData(nil, localCertificate)
else { return [] }
return [certificate]
}
My concern is: Alamofire is asking for only one cert vs I am using three cert flags in curl. How do I translate Alamofire request to the same as curl?
I am using Starscream socket library and am trying to use WSS however I am having a handshake failure. I got my self signed certificate, I converted it to a .der file. Here is the code I am trying
var socket = WebSocket(url: URL(string: "wss://192.168.1.130:6223")!, protocols: [])
override func viewDidLoad() {
super.viewDidLoad()
do
{
let urlPath = Bundle.main.path(forResource: "my_cert", ofType: "der")
let url = NSURL.fileURL(withPath: urlPath!)
let certificateData = try Data(contentsOf: url)
let certificate: SecCertificate =
SecCertificateCreateWithData(kCFAllocatorDefault, certificateData as CFData)!
var trust: SecTrust?
let policy = SecPolicyCreateBasicX509()
let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
if status == errSecSuccess {
let key = SecTrustCopyPublicKey(trust!)!;
let ssl = SSLCert(key: key)
socket.security = SSLSecurity(certs: [ssl], usePublicKeys: true)
socket.delegate = self
socket.connect()
}
}catch let error as NSError
{
print(error)
}
}
So when I try to connect, I get the following error message
2017-07-07 11:06:26.590 CertificateTesting[5180:81661] CFNetwork
SSLHandshake failed (-9807) websocket is disconnected: The operation
couldn’t be completed. (OSStatus error -9807.)
The certificate should work fine, my Android colleague has tried it on his side and has had no issues. The only way I can get it working on my side is if I disable SSL validation like so
socket.disableSSLCertValidation = true
Does anyone have any experience using self signed SSL with sockets. Any information would be much appreciated.
Edit:
I called verify ssl command, it returns
➜ CertificateTesting git:(master) ✗ openssl verify -my_cert.der ca-cert.pem server-cert.pem
usage: verify [-verbose] [-CApath path] [-CAfile file] [-purpose purpose] [-crl_check] [-engine e] cert1 cert2 ...
recognized usages:
sslclient SSL client
sslserver SSL server
nssslserver Netscape SSL server
smimesign S/MIME signing
smimeencrypt S/MIME encryption
crlsign CRL signing
any Any Purpose
ocsphelper OCSP helper
Does that look okay?
You can give a try using the common name in the WebSocket instead of IP.
var socket = WebSocket(url: URL(string: "wss://192.168.1.130:6223")!, protocols: [])
You can verify the common name in the certificate using the command
openssl x509 -in <certificate file> -text
Validate the SSL handshake using following command
openssl s_client -host <common name mentioned in the cert> -port <port> -cert <client_cert file> -key <client_key file> -CAfile <ca_cert file>
in an old swift app, i had the same problem using REST API and WS exposed by a self signed certificate.
Websocket protocol is embedded in HTTP/S protocol, also the handshake.
So generate the certificate .cert:
echo -n | openssl s_client -connect yoururl:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > name_youwant.cert
Import the .cert file in the asset of the app that will use it.
In the class that estabilish the websocket connection, implement URLSessionDelegate.
And then use this logic to validate the self signed certificate:
Note: NSBundle.mainBundle().pathForResource(Config.certificate, ofType: ".cert") -> Config.certificate is a static string that indicate the name of file.
// uncomment if self signed certificate is used on the backend
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let serverTrust = challenge.protectionSpace.serverTrust
let certificate = SecTrustGetCertificateAtIndex(serverTrust!, 0)
let certificateData = SecCertificateCopyData(certificate!)
let remoteCertificateData = certificateData as NSData
let cerPath = NSBundle.mainBundle().pathForResource(Config.certificate, ofType: ".cert")
let localCertData = NSData(contentsOfFile: cerPath!)!
if localCertData.isEqualToData(remoteCertificateData) {
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,NSURLCredential(forTrust:
challenge.protectionSpace.serverTrust!))
}else{
completionHandler(NSURLSessionAuthChallengeDisposition.CancelAuthenticationChallenge,nil)
}
}
Hope this helps.
Regards,
I was using Alamofire 1 with Swift 1.2 and the following request was working:
let instagramUrl = "https://api.instagram.com/v1/media/search?omittedKey=omittedValue"
Alamofire.request(.GET, instagramUrl).responseJSON { (_, _, JSON) in ...
When I updated to Swift 2 and Alamofire 2 I got this error:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSUnderlyingError=0x7d0580f0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorCodeKey=-9802, _kCFStreamErrorDomainKey=3, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x7b7ef7d0>, kCFStreamPropertySSLPeerCertificates=<CFArray 0x7b6805f0 [0x1e64098]>{type = immutable, count = 2, values = (
0 : <cert(0x7b7eee80) s: *.instagram.com i: DigiCert High Assurance CA-3>
1 : <cert(0x7b7ef1f0) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA>
)}}}, _kCFStreamErrorCodeKey=-9802, NSErrorFailingURLStringKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue, NSErrorPeerCertificateChainKey=<CFArray 0x7b6805f0 [0x1e64098]>{type = immutable, count = 2, values = (
0 : <cert(0x7b7eee80) s: *.instagram.com i: DigiCert High Assurance CA-3>
1 : <cert(0x7b7ef1f0) s: DigiCert High Assurance CA-3 i: DigiCert High Assurance EV Root CA>
)}, NSErrorClientCertificateStateKey=0, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x7b7ef7d0>, NSErrorFailingURLKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue}
So I tried to use it:
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"api.instagram.com": .DisableEvaluation,
"*.instagram.com": .DisableEvaluation
]
let manager = Manager(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
manager.request(.GET, instagramUrl).responseJSON { (_, _, JSON) in
and I got this error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue, NSErrorFailingURLStringKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue, NSLocalizedDescription=cancelled}
The last approach I tried is to use a CustomServerTrustPolicyManager, but I have no idea what to add on func serverTrustPolicyForHost(host: String)
class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
override func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? {
var policy: ServerTrustPolicy?
policy = ServerTrustPolicy.DisableEvaluation
return policy
}
}
and replaced the
let manager = Manager(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
to
let manager = Manager(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: CustomServerTrustPolicyManager(policies: serverTrustPolicies)
)
But I got the same error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue, NSErrorFailingURLStringKey=https://api.instagram.com/v1/media/search?omittedKey=omittedValue, NSLocalizedDescription=cancelled}
What can I do to make it work? Preferably the right way.
Thanks.
Either your manager instance is being deallocated because you are not maintaining a reference to it or your credentials you are passing in the query string are invalid.