Private HTTP Live Streaming via CloudFront - ios

I am working on an iOS app which allows downloading and HTTP live streaming of private videos. The videos are stored in an Amazon S3 bucket (as mp4 and segmented as m3u8/ts files). Also CloudFront is turned on and connected to the bucket.
Since the content is private, I need to sign the URLs when connecting via CloudFront. In order to sign the URLs it's necessary to use the private key and therefore it's not possible to generate signed URLs in the iOS app without storing the private key in the bundle. And that would be a bad idea!
So I decided to write a simple Ruby server, which performs the URL signing and redirects to the generated signed CloudFront URL as follows:
http://signing.server.local/videos/1.mp4 → https://acbdefg123456.cloudfront.net/videos/1.mp4??Expires=XXX&Signature=XXX&Key-Pair-Id=XXX
http://signing.server.local/videos/1.m3u8 → https://acbdefg123456.cloudfront.net/videos/1.m3u8??Expires=XXX&Signature=XXX&Key-Pair-Id=XXX
For video downloads it works well, since there is only one request. But when I want the content streamed and give the MPMoviePlayerController the URL of the signing server, only the first request is signed by the server and redirected to CloudFront. For the next requests the MPMoviePlayerController takes the first signed CloudFront URL as the base and tries to connect directly without going throw the signing server.
The paths in the m3u8 files are relative.
Any suggestions how to implement this feature without the need to send all the content through the signing server?

The correct way to do private HLS with S3/CloudFront or any other storage/CDN is to use HLS encryption. See the Apple documentation about this topic.
In addition to the storage where your playlists and segmented video files are stored you have to integrate a secure HTTPS server for storing the top level playlists and keys. These keys are generated during the segmenting using the Apple HLS tools.
Here is how it works:
The MPMoviePlayerController gets an URL pointing to the top level playlist (.m3u8) on the secure HTTPS sever.
In this file there are links to the variant playlists (prog_index.m3u8) which are stored in S3/CloudFront and which point to the video files (.ts).
Additionally the variant playlists contain a link to the keys which are necessary in order to read the video files. These keys are stored on the secure HTTPS server as well.
See the following image:
Taken from the presentation Mobile Movies with HTTP LIve Streaming (CocoaConf DC, Jun '12)
Of course there are possibilities to make the infrastructure more secure, see the linked Apple documentation.
I also created a Ruby script for segmenting to produce the output with given base URLs, which makes things a lot simpler.

Lukas Kubanek has the right answer. However, you can get the effect of signed URLs by putting the top-level playlists in a "private" bucket, and then putting all the other playlists and .ts files in a public bucket. This is pretty much as secure as using signed URLs for everything, in that anyone who wants to can still download and save the content, but can't merely share the URL they were given. They can of course open the top-level playlist and then share a single stream of their choice, or host the top-level playlist themselves, but it's at least a small level of security-by-obscurity that may be enough for your content. Also, if you sign every single segment, you run into a problem with content that's longer than your time limit, or with the user simply pausing the video until the segment links expire.

I think you need some way to avoid doing two requests to different servers for each chunk of video.
Possible solution: Could you change the Cloudfront private key every few minutes? If yes, then just authenticate however you want (bidirectional handshake) and send the app the current private key. If it expires, or if there are any errors due to it expiring at not exactly when expected, just re-authenticate and get new private key.
Possible solution: Talk to authentication server when you want to play video X, and get signed URLs for every part of that video, or even better: a m3u8 file containing signed URLs. Then, play those directly...
Possible solution: Run everything through a local proxy (on loopback interface on the iOS device). Then modify request URLs as needed, or make them redirects.

Related

How do I disable a request for a user name in creating a Azure Media Services video asset using CloudMediaContext.Assets.Create

I have created a asp.net mvc partial view which in conjunction with ajax uploads an mp4 file to Azure blob storage, acquires the resulting blob into an Azure media storage asset, encodes the asset as an adaptivebitratemp4 asset and then publishes that asset. Everything works fine on my local development web site. However when I publish that solution to my Azure development web site the solution stops working. On the local site the code request (through a popup) that I verify my Azure account username; the Azure development site does not make this request and I think the system is "hanging" on my lack of response to this request. I believe that the following call contains the request for verification:
IAsset asset = _context.Assets.Create("NewAsset_" + Guid.NewGuid(), AssetCreationOptions.None);
Where _context is an instance of CloudMediaContext.
Two questions:
I vaguely remember seeing a discussion relating to this scenario but have been unable to find it? Any pointers to the topic?
As any user who can get to this upload view has already been validated as a legitimate user with rights to upload and create their own video, I have no need to do it here. CloudMediaContext.Assets.Create does not seem to require a username. How can I disable this request for a username?
Thank you John. That was it. New to Media Services and made the wrong choice in setting up the account.
How are you doing authentication to Azure Media services ?
Sounds like you used interactive user auth and not service principal for the AAD auth. Web apps should always default to using service principal auth and avoid the pop up.

Why can't Applebot crawl my website?

I am trying to set-up universal links for an iOS app.
The Apple search validator keeps failing when I try to validate the file apple-app-site-association with error message:
Unable to parse that webpage URL. Try a different URL.
The file content is correct, I tried with already validated files from other websites but it seems the crawler fails to access the website generally.
The domain and website are hosted on a shared server at 1and1.com without SSL. The file is not signed.
Any idea why that is?
This is the "App Search API Validation Tool", not the "Universal Links Validation Tool" (which doesn't exist from Apple). The results from this tool have no connection to whether Universal Links work or not.
That said, you must have SSL in order for Universal Links to work. That is the number one requirement. If you can't/don't want to set this up, look at an external link hosting service like Firebase Dynamic Links or Branch.io (full disclosure: I'm on the Branch team)
In reference to "you must have SSL in order for Universal Links to work. That is the number one requirement." This is no longer a requirement.
If your app runs in iOS 9 or later and you use HTTPS to serve the apple-app-site-association file, you can create a plain text file that uses the application/json MIME type and you don’t need to sign it.

Restrict cloudfront signed url (GET Request) to be accessed by my mobile application

I am trying to serve video files using Amazon cloudfront to my app users using signed urls. I have created the signed urls using the documentation and it works perfectly well. The url generated has the signature, expires and keypair_id.
issues
What I am trying to achieve is to serve the video files to the user only when the request is coming in from my particular mobile application. I am looking for a solution to authorize the request (on a signed url) on the cloudfront side.
So if a user tries to access the signed url using our mobile app, we would want to serve the content but if the url is accessed from either web or any other mobile client we would like to raise an authorization error or 404.
I have went through the documentation and a couple blogs looking to achieve the above and everyone has pointed me in the direction to use signed urls which I already am. But the urls are still accessible directly via the browser.
Also I would like to know, why does a signed url has signature as a GET parameter, as if the signature is removed the content is still accessible using the url without the get query params.
Signed Url: http://d2z7g8y6l5f1j0.cloudfront.net/test_upload.mp4?Expires=1456828601&Signature=R3tljkRxGM9se2S4IJT908sT2BBGNJkpWE9IE-v1GAt-QY0WcaEVEY-OYvSSlhFK1ueNcWhgAscJQ7J~qUKZUt3XS5raKU3kj9STKYYzCemRRm1j5DE8XfhjRKRggSSw138F0lr~tDt~TLoJ7Pj9NNvoGl42jNNLaET7~d9pkAGAh-sNpoS1gz~d0CZTo41ZTFMIzshgZNxrWpCOR0PrLHfRALy2H9-Z9w4XfU4v66WEseVQ3FWyeXFyV0UO2S-KIXbe1ODiHFC6Ae6AJlWzoFfIGAxiLymmtUMJgeQHnu80u97ysMbbNYvek-S0tQBkkID3zC~tDQH~EjXPYcNUbA__&Key-Pair-Id=APKAINPV56WSGDECRTPQ
^^^ Serves the content
Original Url: http://d2z7g8y6l5f1j0.cloudfront.net/test_upload.mp4
^^^ Still serves the content
What's the difference in the above urls ?
Further Issue
The signed url that I have generated is still serving the content so what is the point of the expires GET query parameter, or the issue is that I have made the url correctly or not.
I followed the following method to generate my signed url:
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution
# establish cloudfront connection
cloudfront_connection = CloudFrontConnection('AWS_KEY', 'AWS_SECRET')
expiry_time = int(time.time() + 3000)
#get the distribution
distribution = Distribution(connection = cloudfront_connection, domain_name = '<specified_domain_name>', 'id' = '<specified distribution id>')
#create signed url
signed_url = distribution.create_signed_url(url = '<cloudfront_url>', keypair_id = '<cloudfront keypair_id>', expire_time = expiry_time, private_key_file = open('<location>', 'r'))
I have went through the documentation and a couple blogs looking to achieve the above and everyone has pointed me in the direction to use signed urls which I already am. But the urls are still accessible directly via the browser.
Perhaps you have a misunderstanding of the signed URL feature. Any client that has the URL can access the content - there's nothing limiting it to a specific mobile browser or desktop browser or anything else. So long as the URL is valid (e.g. is within the validity period/has not expired, and is within the IP range that you specified, etc), any client will be allowed access.
Your application should generate the signed URL in real time when the user requests it, and it should expire within a time frame that is acceptable to you. This is explained in the docs under How Signed URLs work.
Also I would like to know, why does a signed url has signature as a GET parameter, as if the signature is removed the content is still accessible using the url without the get query params.
You can set up a cache behavior that restricts access to requestors that have valid signed URLs. To summarize, when you set up the distribution, you can configure various cache behaviors based on the path that the user is requesting.
This topic is a bit buried in the documentation. See the docs on Cache Behavior Settings, and in particular the Path Pattern and Restrict Viewer Access subsections.

Protect server URL in mobile app

I know this can sound absolutely stupid, but I could not find any way to solve this problem.
Say I've a mobile app: from this app, after purchasing an item, since the item is downloadable from a server, I make the user download a file to his device. Problem is it looks like it is very easy even for not so smart people, to get the URL of the file, so, without purchasing anything, the not-so-smart-guy can eventually download the same file for free (using a common browser).
Apart from the language I use (it is not important here, it can be JavaScript, Java, Objective-C, whatever), how can I prevent this issue WITHOUT developing an authentication system?
Generate a token for successful purchase, store it in the server side session or database. Add the token as a query string parameter for the file download request. Implement an filter for the file download request to validate the token.
To make sure that the URL is not share able - find some unique attribute of the device that can't be spoofed easily, hash(url,token,unique property) and add it to the url.

How to store a secret API key in an application's binary?

I am creating a Twitter client for Mac OS X and I have a Consumer secret. It's to my understanding I should not share this secret key. The problem is that when I put it as a string literal into my application and use it, like this:
#define QQTwitterConsumerSecret #"MYSECRETYOUMAYNOTKNOW"
[[QQTwitterEngine alloc] initWithConsumerKey:QQTwitterConsumerKey consumerSecret:QQTwitterConsumerSecret];
It is in the data section of my application's binary. Hackers can read this, disassemble the application, etcetera.
Is there any safe way of storing the Consumer secret? Should I encrypt it?
There is no real perfect solution. No matter what you do, someone dedicated to it will be able to steal it.
Even Twitter for iPhone/iPad/Android/mac/etc. has a secret key in there, they've likely just obscured it somehow.
For example, you could break it up into different files or strings, etc.
Note: Using a hex editor you can read ascii strings in a binary, which is the easiest way. By breaking it up into different pieces or using function calls to create the secret key usually works to make that process more difficult.
You could just base64-encode it to obfuscate it. Or, better idea, generate the key instead of just storing it - write something like this:
char key[100];
++key[0]; ... ; ++key[0]; // increment as many times as necessary to get the ascii code of the first character
// ... and so on, you get the idea.
However, a really good hacker will find it no matter what; the only way to really protect it from others' eyes is using a secure hash function, but then you won't be able to retrieve it, too :)
You should not use a secret api key in an application that does not run solely on your server.
Even if it's perfectly hidden.. you can always snoop on the data going through the wire. And since it's your device you could even tamper with SSL (man in the middle with a certificate created by a custom CA which was added to the device's trusted CA list). Or you could hook into the SSL library to intercept the data before actually being encrypted.
A really late answer...
If you setup your own server, you can use it for helping you desktop app getting authorized by users on twitter without sharing (i.e.: embedding) your secret key.
You can use this approach:
When a user installs you desktop app she must register it with twitter and with your server
*)
*) The app asks the server to generate the token request URL
*) The server sends the generated URL to the app
*) The app directs the user to the authorize URL
*) The user authorizes your app on twitter and pastes the generated PIN into it
*) Using the PIN you app grabs the token
*) All further communication uses the token and does not involve your server
Note: the app logs to your server using the user credentials (e.g.: id and password) for your server.

Resources