We´re developing a HTTP-streaming iOS app that requires us to receive playlists from a secured site. This site requires us to authenticate using a self signed SSL certificate.
We read the credentials from a .p12 file before we use NSURLConnection with a delegate to react to the authorization challenge.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[[challenge sender] useCredential: self.credentials forAuthenticationChallenge:challenge];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return YES;
}
By doing this initial connection to the URL where we´re getting the .m3u8 playlist we´re able to play back the playlist using AVPlayer. The problem is that this method only works in the simulator.
NOTE: We´re able to download the playlist using the NSURLConnection on device. This must mean that the AVPlayer somehow can´t continue using the trust established during this initial connection.
We have also tried adding the credentials to the [NSURLCredentialStorage sharedCredentialStorage] without any luck.
Below follows our shotgun approach for that:
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:host
port:443
protocol:#"https"
realm:nil
authenticationMethod:NSURLAuthenticationMethodClientCertificate];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:creds
forProtectionSpace:protectionSpace];
NSURLProtectionSpace *protectionSpace2 = [[NSURLProtectionSpace alloc]
initWithHost:host
port:443
protocol:#"https"
realm:nil
authenticationMethod:NSURLAuthenticationMethodServerTrust];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:creds
forProtectionSpace:protectionSpace2];
EDIT: According to this question: the above method doesn´t work with certificates.
Any hint to why it doesn´t work on device, or an alternate solution is welcome!
From iOS 6 onwards AVAssetResourceLoader can be used for retrieving an HTTPS secured playlist or key file.
An AVAssetResourceLoader object mediates resource requests from an AVURLAsset object with a delegate object that you provide. When a request arrives, the resource loader asks your delegate if it is able to handle the request and reports the results back to the asset.
Please find the sample code below.
// AVURLAsset + Loader
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
AVAssetResourceLoader *loader = asset.resourceLoader;
[loader setDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
// AVPlayer
AVPlayer *avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
You will need to handle the resourceLoader:shouldWaitForLoadingOfRequestedResource:delegate method which will be called when there is an authentication need and you can use NSURLConnection to request for the secured resource.
(BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
//Handle NSURLConnection to the SSL secured resource here
return YES;
}
Hope this helps!
P.S : The proxy approach using CocoaHTTPServer works well but using an AVAssetResourceLoader is a more elegant solution.
It seems that until Apple lets us control what NSURLConnections the AVPlayer uses the only answer seems to be to implement a HTTP loopback server.
To quote the apple representative that answered our support question:
Another option is to implement a loopback
HTTP server and point client objects at that. The clients can use
HTTP (because their requests never make it off the device), while the
loopback HTTP server itself can use HTTPS to connect to the origin
server. Again, this gives you access to the underlying
NSURLConnections, allowing you to do custom server trust evaluation.
Using this technique with UIWebView is going to be tricky unless you
completely control the content at the origin server. If the origin
server can return arbitrary content, you have to grovel through the
returned HTTP and rewrite all the links, which is not much fun. A
similar restriction applies to MPMoviePlayerController/AVPlayer, but
in this case it's much more common to control all of the content and
thus be able to avoid non-relative links.
EDIT:
I managed to implement a loopback server using custom implemenations of the
HTTPResponse and HTTPConnection classes found in CocoaHTTPServer
I can´t disclose the source, but I used NSURLConnection together with a mix of the AsyncHTTPResponse and DataHTTPResponse demonstration responses.
EDIT:
Remember to set myHttpServerObject.interface = #"loopback";
EDIT: WARNING!!! This approach does not seem to work with airplay since the airplay device will ask 127.1.1.1 for encryption keys. The correct approach seems to be defined here:
https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/AirPlayGuide/EncryptionandAuthentication/EncryptionandAuthentication.html#//apple_ref/doc/uid/TP40011045-CH5-SW1
"Specify the keys in the .m3u8 files using an application-defined URL scheme."
EDIT:
An apple TV and iOS update has resolved the issue mentioned in the edit above!
Related
In my app I need to call some REST API service calls. The certificate on the target development server where REST API services are deployed is self signed. So when I am running app I am getting error like:
Failed to load resource: The certificate for this server is invalid. You might be connecting to a server that is pretending to be “192.168.10.20:8080” invalid.....which could put your confidential information at risk.
As this server is only for dev/testing purpose so I simply wants to ignore ssl check ... How can I achieve it? I tried following way:
[AppDelegate.m file] but didn't succeeded as below code is not working in iOS 11 ...
#implementation NSURLRequest(DataController)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
#end
I am using ionic 3 & Cordova 7 in my app.
Thanks #peter I have found one more workaround for checking app in ios11 for testing purpose whether API's are getting hit properly or not. You can forcefully change the webview from WKWebView to UIWebview by adding below tag in config.xml
<preference name="CordovaWebViewEngine" value="CDVUIWebViewEngine" />
now add following code in Appdelegate.m file
#implementation NSURLRequest(DataController)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString *)host
{
return YES;
}
#end
it worked for me..!
Note:only for dev/testing purpose.not recommended for production deployments
Interestingly, I am just researching the same problem. Looks like in iOS 11 things are a bit more restricted. I am answering here for WKWebView.
In essence you need to do:
place custom auth code to the WKWebView plugin code
load resource directly from Cordova (then WKWebView events gets properly triggered)
disable ATS (NSAppTransportSecurity)
Detail description
What you should do in detail is the following (if you are using WKWebView):
You need to modify CDVWKWebViewEngine.m (plugin code). You need to add there:
- (void)webView:(WKWebView *)webView
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge
*)challenge completionHandler:(void (^)
(NSURLSessionAuthChallengeDisposition
disposition, NSURLCredential *credential))completionHandler {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
completionHandler(NSURLSessionAuthChallengeUseCredential,
[NSURLCredential credentialForTrust:serverTrust]);
}
However, please note - this only works when WKWebView is initialized (i.e. loaded via cordova framework).
So you need to load your application also from that URI where API is. I presume you have local network (self signed certificate), so this should not be a problem. If you will load application locally (i.e. from index.html) then this wont work!
Additionally you need to disable iOS ATS in application *.plist setting file like:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
This is what works for me.
Additional resources:
Very nice testing site for different SSL scenarios (including self signed): https://badssl.com/
Apple forum discussion on this topic: https://forums.developer.apple.com/message/269452#269452
Disclamer: Disabling certificate check should be avoided, use this only if you have a very good reason or other restrictions. You still have communication security, but you have no trust. Hence men in the middle attack is possible! If you decide to go with this option, you should also use certificate pinning to make things more secure.
I recently had a problem associated with an invalid SSL certificate on a server.
In my application, I am using the WKWebView plugin, which has become a requirement to send applications to an App Store. It is in this plug-in that an adjustment needs to be made to ignore invalid SSL certificates, in the file "plugins/cordova-plugin-ionic-webview/src/ios/CDVWKWebViewEngine.m", includes:
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSLog(#"Allow all");
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
CFDataRef exceptions = SecTrustCopyExceptions (serverTrust);
SecTrustSetExceptions (serverTrust, exceptions);
CFRelease (exceptions);
completionHandler (NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
}
As a result, the application will not take into account invalid SSL certificates.
References of https://developer.apple.com/forums/thread/15610
Remembering that it is not recommended to do this in applications in production, because invalid SSL certificates can compromise the security of the application.
I am using the socket.io framework for a chat module in Objective-C code base. The connection however loses very frequently, sometimes right after the connection is made, it's disconnected. I have looked for background app and incoming call scenarios as answered in SO, but here it happens even if app is active and chat is in process.
Here is how I make the connection:
NSURL* url = [[NSURL alloc] initWithString:#"http://domainAddress:port"];
self.socket = [[SocketIOClient alloc] initWithSocketURL: url config: #{#"connectParams": #{#"user_id": [NSNumber numberWithInt:user_id]}}];
[self.socket setReconnects:YES];
I used the setReconnects: method as initially the socket was not reconnecting itself.
Please let me know how to resolve the issue and how can I debug more further.
In an iPhone app that plays live video from a server via HTTP Live Streaming, is it possible to access decoded video frames after decoding?
As far as I can see AVPlayer, MPMoviePlayer, and CoreVideo do not seem to provide a callback to notify the app that an individual frame has been decoded.
My question is similar to "Record HTTP Live Streaming Video To File While Watching?", except I'm not necessarily interested in full DVR functionality. The one answer there suggests a server-side solution and is vague about the possibility of a client-side solution. It's also similar to "Recording, Taking Snap Shot with HTTP Live streaming video running MPMoviePlayerController in iOS" except that I don't require a solution to work with MPMoviePlayerController.
It's possible using "AVPlayerItemVideoOutput" like this:
NSDictionary *options = #{ (__bridge NSString *)kCVPixelBufferPixelFormatTypeKey : #(kCVPixelFormatType_32BGRA),
(__bridge NSString *)kCVPixelBufferOpenGLESCompatibilityKey : #YES };
myOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:options];
myOutput.suppressesPlayerRendering = NO;
[myOutput requestNotificationOfMediaDataChangeWithAdvanceInterval:1];
[myOutput setDelegate:self queue:dispatch_get_main_queue()];
[playerItem addOutput:myOutput];
I've done it in several of my projects.. Take care and good luck!
FYI: (AVPlayerItem *playerItem;)
/Anders.
I have been struggling with an issue where NSURLConnection calls instantly fail. The device needs to be rebooted entirely or Flight Mode needs to be toggled on/off to resolve the problem. Restarting the app (swipe up) alone does not help.
Some facts:
-All URLs are HTTPS, TLS 1.2 compatible with Forward Secrecy. There are no issues with ATS and iOS 9. The error has been present since iOS 7 and remains on 9.2.
-No third party frameworks are used by the app. I use only native NSURLConnection calls that always work, except for when this odd situation occurs.
-No infrastructure/network issues - other devices on same networks (same WiFi for instance) work in the same app at the same time. Going to/from 3G/Wifi makes no difference.
-I always implement willCacheResponse to return nil.
-The service is hosted on AWS Elastic Beanstalk, so some suggested that it might be a DNS caching issue in case of IP address changes - this seems unlikely to me and should trigger multiple errors at once on different devices, which I have never seen.
-The method called is didFailWithError, instantaneously, as if there were no Internet connection on the device at all - all other apps work, however.
-The website that hosts the API used by the app can be browsed with no problems at all times. The website actually makes the same requests to fetch data.
The error code returned is -1003, kCFURLErrorCannotFindHost. I've been following a thread on Git dealing with the same issue to no avail. https://github.com/AFNetworking/AFNetworking/issues/967
I tried using NSURLRequestReloadIgnoringCacheData for all my requests, but that did not help.
With this information, will anyone care to venture a guess what I might be doing wrong? I added the bounty because I have no idea how to approach this problem - especially because it's so inconsistent. And it is definitely not a legitimate error (that is, that the domain could not be found), as the service is operating fine while this happens on random clients.
I create my request with a static method that looks like this. It's been stripped of some non-public info, but basically it just performs a POST request with JSON data. [Controller getSQLHost] just returns a URL - the base domain.
+(NSURLConnection*)initiatePOSTwithJSONDictionary:(NSDictionary*)dictionary toURL:(NSString*)urllocation withDelegate:delegate {
NSMutableDictionary *connectionDictionary = [[NSMutableDictionary alloc] init];
if (dictionary) {
[connectionDictionary setObject:dictionary forKey:#"input"];
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:connectionDictionary options:kNilOptions error:nil];
NSURL *url = [NSURL URLWithString:[[Controller getSQLHost] stringByAppendingString:urllocation]];
NSString *postLength = [NSString stringWithFormat:#"%i", (int)[jsonData length]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:jsonData];
return [[NSURLConnection alloc] initWithRequest:request delegate:delegate];
}
Does you delegate implement connectionShouldUseCredentialStorage ? (or respond with YES)
I think the device's keychain is used when this method returns yes which may explain the persisting failure beyond the life time of the running application and why rebooting or otherwise resetting network connectivity "fixes" it. If an authentication failure has been recognized once, it may linger in the key chain for a little while which would then respond immediately without actually going to the server.
What would cause the authentication to register as a failure in the keychain in the first place may depend on a variety of factors. It could be as simple as a typo in the saved password or more convoluted such as some certificate expiration preventing the SSL layer from establishing a secure link.
You're creating NSURLConnections on the current runloop. How many are active at any one time? Have you considered using an NSOperationQueue so that you don't get bitten by load bugs?
Is your delegate thread-safe? If not, that could explain the sporadic-ness of the bug.
You say you don't see the problem often, but others do. Can you borrow their devices and maybe even them and their usage patterns and thus get to see the problem more often?
I am going to implement video chat using Opentok by following the post http://www.iphonegamezone.net/ios-tutorial-create-iphone-video-chat-app-using-parse-and-opentok-tokbox/
I have implemented parse.com for the backend which is responsible for session and token creation for opentok
When I am running the code, It creates SessionId,active users (Which I can see in the parse.com's backend)
But When I am trying to connect to opentok with help of following code, Error message coming stating that "The Session failed to connect"
_session = [[OTSession alloc] initWithSessionId:sessionID
delegate:self];
[_session addObserver:self forKeyPath:#"connectionCount"
options:NSKeyValueObservingOptionNew
context:nil];
[_session connectWithApiKey:kApiKey token:token];
if any one know how to solve this problem then help.
Or any suggestion also appreciated.
Yes,Now I got the solution.
Firewall was not allowing me before to connect the session.
I allowed the firewall connection by providing network ID and password.
Now It is working for me.