Why isn't CFStream using TLS 1.3? - objective-c

I'm trying to figure out why an iPhone running iOS 14 isn't using TLS 1.3 to connect to a compatible web server.
Relevant code is:
- (void) streamOpened:(NSStream *)stream {
NSDictionary *settings = #{
(__bridge NSString *)kCFStreamSSLValidatesCertificateChain: (__bridge NSNumber *)kCFBooleanFalse
};
CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
}
The full source can be seen here: https://github.com/tls-inspector/tls-inspector/blob/app-store/CertificateKit/Getters/CKAppleCertificateChainGetter.m
I've tried specifying the SSL Level with kCFStreamSSLLevel set to kCFStreamSocketSecurityLevelTLSv1_3 but that didn't do anything.
If I use OpenSSL to connect it uses TLS 1.3 and I can verify that with a packet capture, but using CFStream it sticks to 1.2.

The short answer is that you end up using a deprecated API that does not support TLS 1.3.
The long answer, which details a potential solution, is given below.
I tried to solve this using CFStream but did not succeed.
It might be possible. The problem is that you end up, as you do, at a low level using SSLConnectionRef and friends and at a higher level using NSInputStream and NSOutputStream and friends and at some point you run into this https://developer.apple.com/documentation/security/secure_transport?language=objc legacy API.
On that page it mentions that that API was replaced by the Network framework and really this is what I am suggesting you should use. I was hoping to also implement a solution quickly but it needs a bit more rework than what SO is for so I leave it at that.
However, herewith some suggestions, basically the ideas I was hoping to implement.
As before, there are two levels.
At the lower level you end up using the nw_ and family but I would say do not go that way. It might be required for some specialised needs and your app might be in that category, but still, just take note that the higher level is built on top of this.
At the higher level, and this is where I think your solution lies, you end up using NSURL and friends. Your goto guy here is probably NSURLSession but the implementation will dictate that. I tried to give an outline and you can look at my code to get more detail but I think you will be in a much better position to implement from here onwards.
In that I was hoping to connect your legacy stream code to NSURLSession but when that failed I stopped. That is perhaps a bit optimistic and I think it requires a more serious rework, but the various delegates (NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionStreamDelegate and so on) seem to be ready and waiting for your solution and I don't think it is a lot of work actually.
The most relevant snippet of code from my attempt is below. This switches on TLS 1.3 and I tried to implement around this.
// Some configuration
NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
// Note this one!!!
config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:NSOperationQueue.currentQueue];
From this point onwards I tried to reuse your stream code but to be honest I think the final solution may not even use streams any more, and in stead just rely on using the correct delegate. Forgive me for getting excited at this point, but I suspect you will be able to greatly simplify your code as a result.
I enjoyed your app - it is pretty polished and I look forward to you solving this problem. I also enjoyed toying with it but for now that is my contribution.
First attempt
I eyeballed your code - it needs some reworking for TLS 1.3. I tried to do it but I am now rewriting that class so I stopped. You can do it, but, I can not guarantee that it will work!
Anyhow, here are some thoughts.
First on the existing code. Just some - ahem - observations, nothing serious ...
Note that your streamOpened will apply the settings no matter which stream was triggered. The delegate will message that twice I believe, once for the input and once for the output stream. Although here it seems it does not matter you should be careful as that could introduce some serious bugs in another situation.
Also, I think you need to configure the streams before they are opened but this did not make any difference. If you configure the streams before they are opened in your performTaskForURL or afterwards in the streamOpened it did not matter.
I played with the configuration a bit. You need not set one on the output stream, only on the input. And the only key that is required is the one you already set. I could not get any difference no matter what I did.
Second, the solution I - ahem - think will work here.
You need to configure the URL session. So what I did was the following
- (void) performTaskForURL:(NSURL *)url{
queryURL = url;
// Some configuration
NSURLSessionConfiguration * config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
// Note this one!!!
config.TLSMaximumSupportedProtocol = kTLSProtocolMaxSupported;
NSURLSession * urlSession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:NSOperationQueue.currentQueue];
// Just code to test the idea, not production ready I know
NSURLSessionStreamTask * streamTask = [urlSession streamTaskWithHostName:url.host
port:443];
[streamTask captureStreams];
}
- (void)URLSession:(NSURLSession *)session
streamTask:(NSURLSessionStreamTask *)streamTask
didBecomeInputStream:(NSInputStream *)inputStreamUrl
outputStream:(NSOutputStream *)outputStreamUrl
{
// I was hoping to get away with this,
// just setting your streams equal to the
// URL stream task streams but it did not
// work ... problem is you need more of the
// stream task delegate methods I believe
inputStream = inputStreamUrl;
outputStream = outputStreamUrl;
// This is some left over code from your performTaskForURL message
// Here you can see how I toyed with the stream configuration
// Configure here before it is opened
// Pretty much your current streamOpened message
// Note only input needs be configured (fwiw)
[self configureStream:inputStream];
inputStream.delegate = self;
outputStream.delegate = self;
[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
[inputStream open];
// I was hoping this would just work but I think it needs more work
}
The idea here is to configure a URL session with kTLSProtocolMaxSupported and then to create a stream task off that. Now this is where I want to stop and think you are much better at going further. I was hoping I can just get the streams and throw them back to your code, but it did not work and for now I'm not going further.
I could not test my idea as I need to hook up a few more things to the NSURLSessionStreamDelegate that your class now also implements. I think you do the same in the class already, but for streams.
Now, again, I may be wrong, and wanted to test before I posted, but either see if this might work and then I think you need to e.g. implement delegate methods such as URLSession:task:didReceiveChallenge:completionHandler:.
I think you already do that sort of thing in your code, but this is why I am stopping here, as I think you will be a much better judge of this idea.
I've toyed with it some more but no success - but I think you need to use NSURLSession here, maybe even in stead of sockets.

Related

connection:didReceiveResponse: from NSURLConnectionDelegate never fired

I'm trying to get UIProgressView, NSURLConnection, NSOperation and NSOperationQueue working together.
Here is the code: http://pastie.org/4080576
Problem:
connection:DidReceiveData: never called: execution going immediately to -start(), through -main(), then to -connection:DidReceiveResponse:, back to -main() (sometimes), and finally to -connectionDidFinishDownloading:destinationURL:.
When I trying to download picture from this link:
http://upload.wikimedia.org/wikipedia/commons/4/41/Kharkov_picture_1787.jpg
I get this output:
2012-06-13 19:43:06.189 downloadingFilesInOpQueue[5070:f803] Received
response: 2012-06-13 19:43:06.190
downloadingFilesInOpQueue[5070:f803] Filesize is: 3075638 Suggested
filename is: Kharkov_picture_1787.jpg 2012-06-13 19:43:12.476
downloadingFilesInOpQueue[5070:f803] Finished downloading. Received 0
bytes
Also, I can't figure out where connection:didReceiveResponse: belong:
NSURLConnectionDelegate or NSURLConnectionDataDelegate.
P.S. If there some style issues, I would glad to hear about them. Thx.
I strongly suspect your "busy" while-loops are consuming the underlying run-loop's execution and choking out the NSURLConnection from being able to handle data as it arrives.
Looking at your pastie.org code, here's your primary culprits:
while (![self isFinished]) {
...
while ([self fileSize] == 0) {}
while ([self receivedDataLength] == 0) {}
...
}
Busy loops like this should almost never be used in iOS. iOS programming is largely event-driven, especially NSURLConnection. Instead, update your UIProgressView in response to connection:DidReceiveData: etc.
Honestly, coding your own NSURLConnection handlers is kind of a messy pain. I strongly recommend you look into using an open-source library that handles some of the difficulties. Here's two decent ones to check out:
https://github.com/AFNetworking/AFNetworking
https://github.com/pokeb/asi-http-request

localStorage not persisting in OSX app (Xcode 4.3)

From what I have seen, if you are building a OSX desktop HTML5 app and want localStorage to persist in your WebView wrapper, you need to do something like this:
WebPreferences* prefs = [webView preferences];
[prefs _setLocalStorageDatabasePath:#"~/Library/Application Support/MyApp"];
[prefs setLocalStorageEnabled:YES];
Taken from: How do I enable Local Storage in my WebKit-based application?
But this doesn't seem to work for me in Xcode 4.3. Instead I get
"No visible #interface for 'WebPreferences' declares the selector '_setLocalStorageDatabasePath:'
"No visible #interface for 'WebPreferences' declares the selector 'setLocalStorageEnabled:'
I'm very new to Objective C, and are probably doing something silly like not including some header or something.
I've included the WebKit framework and both of these headers:
#import <WebKit/WebKit.h>
#import <WebKit/WebPreferences.h>
And what's weird is that I can access other methods of prefs, i.e. [prefs setDefaultFontSize:10] - but just not the two above that I listed.
Any ideas? Is this something that has been removed in Xcode 4.3?
OK, I have a solution. I looked at the source code to macgap and noticed how they were dealing with this issue.
It turns out the error message I was getting does make a little sense - I needed to declare an interface for WebPreferences first.
#interface WebPreferences (WebPreferencesPrivate)
- (void)_setLocalStorageDatabasePath:(NSString *)path;
- (void) setLocalStorageEnabled: (BOOL) localStorageEnabled;
#end
...
WebPreferences* prefs = [WebPreferences standardPreferences];
[prefs _setLocalStorageDatabasePath:"~/Library/Application Support/MyApp"];
[prefs setLocalStorageEnabled:YES];
[webView setPreferences:prefs];
Like I said, I'm new to Objective-C. I don't really get why the interface is needed in order to call those two methods (i.e. when I can call the other methods without the interface).
There's good news and there's bad news; I'm going to make the assumption that you want the bad news first (it'd be easier if I answered your question with the bad news first anyway).
The Bad News
The only answer to why this is happening is that Xcode 4.3 doesn't offer those methods anymore. That question that you linked to, "How do I enable Local Storage in my WebKit-based application?" was last active over a year ago (with the accepted answer being edited in early 2011). There have been at least two updates to Xcode since then (probably more and I'm just not remembering them), and it seems feasible to me that Apple would want to keep their private methods private, so it's safe to assume that they removed them as well as the support for setLocalStorageEnabled:.
The reasons that I don't think that there is any other answer to your problem are the following:
Both methods that you call on the WebPreferences instance are not supported. It's not just the private method, so Apple must have modified the WebPreferences class, removing not only setLocalStorageEnabled: but also support for private methods such as _setLocalStorageDatabasePath:. Why they supported private methods to begin with, I don't know, but they've definitely cracked down on their support because I haven't seen an opportunity to implement a private method in quite some time.
If implementing the private method (or even the other, public method) were possible, it'd be as easy as your code makes it out to be. If one looks at the linked question, they don't mention any difficult steps to getting the code to be supported. There isn't any way to import a private part of a framework such as WebKit without doing some heavy-lifting with regards to not only finding the private part but getting it into your code as well. Even if you can get those methods supported in your code after all of that heavy-lifting, it'd be highly unlikely that Apple would be very happy with it and they'd probably deny your app from the app store.
Sorry to be a Debbie-downer about it all, but I just don't think that your code would work anymore without some deep diggging and large workarounds. If you want it to work easily, you'll probably have to go back to early 2011 and make your app then instead.
The Good News
There is probably some solution that doesn't involve private and unsupported methods that I'm just not aware of because of my lack of experience using WebKit. Instead of looking for an answer for why your code isn't working, I'd start looking for alternatives for what your code is supposed to do.
I have found a solution to the persistence problem. see my post at
Local Storage in webview is not persistent

NSURLConnection messes up iPad memory

we build an iPad app that downloads a bunch of data and PDF documents from a web service (data first, documents later in the background). To do so, we use SOAP via HTTP(S) requests. It works fine and altogether, the app is running well. Problem is, if there are too many documents to download at some point the app crashes. Using Instruments I figured out that it is a memory issue, particularly NSRegularExpression and NSRunLoop. (I'm using ARC)
I could improve my code to optimize the NSRegularExpression creation. But I don't know how to improve the NSRunLoop issue.
I tried both, asynchronous and synchronous HTTP request. Using async, I had to wait for the download to finish and since sleep()/[NSThread sleepForTimeInterval:] aren't an option, I use
while ( _waitToFinish ) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
Using sync request, Instruments reveals that
[NSURLConnection sendSynchronousRequest:theRequest returningResponse:&urlResponse error:&error];
also "waits" with help of NSRunLoop and also messes up the memory.
Is this a bug in CoreFoundation or ARC?
Is there another way to idle while waiting for the requests to finish?
Thanks in advance.
Edit:
With "memory issue" I meant that the app crashes (or is killed by iOS) because it uses too much memory.
This is what Instruments shows:
The percentage get higher the longer the app is downloading.
Edit:
Going further down revealed that it is NSURLConnection, that is messing up the memory. It seems that I have somehow missed setting connection and receivedData to nil (see URL Loading System Programming Guide). This improved my memory usage again a little.
Now, there are two more big memory allocation operations:
And this is the code I think belongs to what Instruments displays:
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_receivedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseText = [[NSString alloc] initWithBytes:[_receivedData mutableBytes] length:[_receivedData length] encoding:NSUTF8StringEncoding];
self.lastResponse = responseText;
responseText = nil;
connection = nil;
_receivedData = nil;
_lastResult = TRUE;
_waitToFinish = FALSE;
}
Is there anything I could change to improve the code?
Edit: (Changed title from "NSRunLoop messes up iPad memory")
Edit:
I created a test app to prove that it is the NSURLConnection, that messes up the memory. Then I contacted the Apple Developer Support.
Since I am downloading a lot of PDF in an iteration with NSURLConnection, the solution was to add an #autoreleasepool { .. } in the iteration and another one around the NSRunLoop.
Thanks.
It's not a bug in Core Foundation or ARC. It's a bug in your code.
Don't run the run loop yourself. Just use +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:]. It will call your completionHandler block when the request is complete. That's when you process the response. Meanwhile, just return out of your method and let the system worry about running the run loop.
You say “it's a memory issue” with NSRegularExpression and NSRunLoop, but you don't say what the issue is, or what evidence Instruments is showing you. If you describe the “issue” in more detail, maybe we can help you with that.

parsing xml in UITableViewCell after receiving response from server

Hello friend, I want ask something how I can return Conn.xmlParser
because the connectionDidFinishLoading is void and I need this result in order parsing in UITableView how I can use the value of Conn.mlParser?
Please need help.
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"DONE. Received Bytes: %d", [Conn.webData length]);
ta=Conn.webData;
if( Conn.xmlParser )
{
[Conn.xmlParser release];
}
Conn.xmlParser = [[NSXMLParser alloc] initWithData: Conn.webData];
[Conn.xmlParser setDelegate: self];
[Conn.xmlParser setShouldResolveExternalEntities: YES];
[Conn.xmlParser parse];
[connection release];
[Conn.webData release];
}
Make sure your delegate also implements the NSXMLParserDelegate protocol. Then you can call didStartElement, didEndElement, didEndDocument, etc.
I usually create an NSArray (or NSMutableArray) that then is populated by objects of the type you are retrieving in the webdata using the NSXMLParser methods mentioned above. Use this array as the datasource in the UITableView's cellForRowAtIndexPath also.
When you reach didEndDocument just call [myUITableView reload] to update the table view. (Unless the tableview and the connection delegate are in two different classes, then you should use KVO to let the tableviewdelegate know the data has been retrieved).
FOLLOW UP:
Rather then just give you are example (of which there are already several on the web), which will just leave you with more questions, I'm going to try to explain what it is that you should understand if you are going to be a successful iOS developer.
And if the steps below seem too daunting for you I suggest you step back and try to tackle the problem slowly one piece at a time. If you still aren't making progress, then go buy a copy of Beginning iPhone 4 Development by Mark, Nutting and LaMarche and then come back to this when you understand the underlying process of Objective-C and iPhone programming better.
To accomplish your goal:
First, you should ask yourself what you are trying to accomplish.
You are getting data from a website (webservice perhaps?) and you want to decode (parse) that information and display it in a tableview. Correct?
So, to solve your problem you have to break apart the question and make sure you understand how to do each part of your task.
Create a seperate Class modeled after the type of data you are going to recieve from your website. If you are getting car objects that specify color, year, model then you should create a Car class with color, year and model properties.
Get Data from a website. You seem to have some idea how to do this since you are already capturing a connection event in the code you showed above. However, do you understand what is happening in ConnectionDidFinishLoading?
Parse the data returned from the website. This requires the use of an XML parser, again you seem to at least know that you need to do something here to parse the data. It also requires a place to store the data that is returned from the website. An array of objects of the type you create in step 1 will be your storage area for the returned and parsed data. Finally, it requires that you understand the specifics of the data that is being passed to you from the website so you understand how to parse the data and store it correctly (and that it really is in XML format, else XMLparsing will not work here).
Display the parsed data in your UITableView. This requires you to understand how to display data in a tableview and how to set the datasource for the tableview.
Second, you should look for sample code you can study that does what you are trying to accomplish and read Apple's documentation to get a firm grip on what has to happen to create your desired result. Look at each part of your task and wherever there is something that is unclear, study that specific issue. How do I use NSXMLParser? How do I display data in a UITableView from an array? etc.
If you don't take the time to really understand how to solve this problem, rather then just copying code, then you will just be back tomorrow with another problem.
Hopefully this will help you solve this problem and help you understand how to solve future problems.
And to get you started, I will give you a link that more or less answers your question.
iPhone Tutorial Creating an RSS feed Reader

How to avoid reference count underflow in _NSCFURLProtocolBridge in custom NSURLProtocol in GC environment

The basics are I have a custom NSURLProtocol. In startLoading, [self client] is of type:
<_NSCFURLProtocolBridge> {NSURLProtocol, CFURLProtocol}
The problem is running this in a garbage-collected environment. Because I'm writing a screensaver, I'm forced to make it garbage-collected. However, the _NSCFURLProtocolBridge protocol seems to always throw:
malloc: reference count underflow for (memory_id_here), break on auto_refcount_underflow_error to debug
An example dump to the debug console is:
ScreenSaverEngine[1678:6807] client is <_NSCFURLProtocolBridge 0x20025ab00> {NSURLProtocol 0x200258ec0, CFURLProtocol 0x20029c400}
ScreenSaverEngine(1678,0x102eda000) malloc: reference count underflow for 0x20025ab00, break on auto_refcount_underflow_error to debug.
You can see that the underflow occurs for <_NSCFURLProtocolBridge 0x20025ab00>.
When I break on auto_refcount_underflow_error, it seems to stack-trace back up to URLProtocolDidFinishLoading: in:
id client = [self client];
...
[client URLProtocolDidFinishLoading:self];
This problem seems to have existed for a while, but there seems to be no answer at all online:
http://lists.apple.com/archives/cocoa-dev/2008/May/msg01272.html
http://www.cocoabuilder.com/archive/message/cocoa/2007/12/17/195056
The bug only shows itself in garbage-collected environments for these listed bugs as well. Any thoughts on how I can work around this without causing memory issues? I'm assuming this probably has something to do with the CF type underneath NSURLProtocol being released improperly?
Last WWDC we confirmed this bug with a webkit engineer, he could see the bug right there in the code so hopefully they'll fix it. The workaround is to CFRetain the client in the initWithRequest method.
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client
{
// work around for NSURLProtocol bug
// note that this leaks!
CFRetain(client);
if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client])
{
}
return self;
}
It is a bug in the implementation of _NSCFURLProtocolBridge.
Please use http://bugreport.apple.com/ and file a bug. If you include the URL to this page, that would be appreciated (and if you update this page with the Radar #, that would be appreciated, too). Ideally, if you can attach a binary of your screensaver, that would be very helpful; no source needed.
Fortunately, it should not cause a crash. Unfortunately, it probably causes a leak.
This error generally indicates that an object was retained with -retain, but released with CFRelease(). If you believe that this could not be your object (and that's not a terrible belief), then you should open another Radar. But you should first look around and see if there's a CF object you're using -retain on when perhaps you should use CFRetain().
The rest of this is shooting in the dark....
You may gain some insight by stepping up the stack and looking at the parameters that are being passed to these C++ methods (or particularly auto_zone_release). Try this in gdb to try to see what's in the first parameter:
p *($esp)
And see if you can get any insight about the object being passed. Perhaps this will work if you're lucky:
po (id)(*($esp))
I worked around this issue by CFRetain-ing the client, and CFRelease-ing it again on the next call to startLoading
-(void)startLoading
{
if ( client ) CFRelease(client);
client = [self client];
CFRetain(client);
and, of course, in finalize
-(void)finalize
{
if ( client ) CFRelease(client);
[super finalize];
}
client is an instance variable of the NSURLProtocol subclass.
Here is the bug report I had filed a while back:
http://openradar.appspot.com/8087384
Probably worth filing as well, it's already been dup-ed, but it would be nice to get it fixed.
As Alex said, an Apple developer looked at the source code in front of me and located the issue easily with the example we had.
the same error comes sometimes using NSURL in open dialog filter.
for me it was enough to set it to nil explicitly after I don't need it anymore.