Question

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!

Was it helpful?

Solution

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.

OTHER TIPS

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!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top