I recently signed up for the Beta program of Pusher, a realtime push notification service powered by HTML5 WebSockets. Pusher provides a RESTful web service to which you can POST client notification messages. The Pusher web service engine then relays those notifications to all clients that have subscribed to your "pusher application" over HTML5 WebSockets. From what I have read, WebSockets allow the browser to open up a direct line of communication with the server outside of the standard request-response life cycle. The server can then "push" information through the WebSocket, directly to the browser, without the browser having to go out and request it (typically via polling).
In an effort to learn more about the world of HTML5, I thought I would create a simple "Hello World" type example powered by the Pusher service. As it turns out, though, even "simple" in this case is not all that simple. The complexity of the Pusher interaction lies in the POST to the RESTful web service. The POST can be performed by a straightforward CFHTTP call; but, the POST itself had to be digitally signed using a shared-key-encrypted version of the message. I don't know about you, but I find cryptography to be anything but straightforward. And, granted I'm no security expert, but the need to sign your POST just seems a bit excessive (not to mention it literally doubles the length of your code). Of course, more likely than not, that might just be my frustrated ignorance talking.
That said, for this example, I created a small ColdFusion form that accepts a message. Upon submit, ColdFusion posts that message via CFHTTP to my Pusher application. I then have another page that opens a WebSocket connection to the Pusher service for the given channel and binds to my "helloWorld" event. When that helloWorld event is triggered, the event handler simply adds the given message to the receiver's page.
Here is the ColdFusion page that takes the message and POSTs it to the Pusher application:
<!--- Param the form value. ---> <cfparam name="form.message" type="string" default="" /> <!--- Check to see if the message is available (if the user submitted the form). ---> <cfif len( form.message )> <!--- Pusher information used to tie this push notification to a given application and to a given client (listening for a given channel). ---> <cfset pusherAppID = "1306" /> <cfset pusherKey = "61b7e858d47e67683bd8" /> <cfset pusherSecret = "****************" /> <cfset pusherChannel = "helloWorld" /> <!--- Event we are triggering (this is what the client will "bind" to on the given channel). ---> <cfset pusherEvent = "hellowWorldEvent" /> <!--- Data we are actually "pushing". All data must be sent as JSON (although it doesn't have to be simple string values). ---> <cfset pusherMessage = serializeJSON( form.message ) /> <!--- Authentication information. ---> <cfset authVersion = "1.0" /> <cfset authMD5Body = lcase( hash( pusherMessage, "md5" ) ) /> <cfset authTimeStamp = fix( getTickCount() / 1000 ) /> <!--- Build the post resource (the RESTfule resource). ---> <cfset pusherResource = "/apps/#pusherAppID#/channels/#pusherChannel#/events" /> <!--- ------------------------------------------------- ---> <!--- ------------------------------------------------- ---> <!--- The following is the digital signing of the HTTP request. Frankly, this stuff is pretty far above my understanding of cryptology. I have adapted code from the PusherApp ColdFusion component written by Bradley Lambert: http://github.com/blambert/pusher-cfc To be 100% honest with you, I find this step to be very frustrating. Do we really need so much security around this? ---> <!--- Create the list of query string values (alpha-sorted with no URL encoding). ---> <cfsavecontent variable="queryStringData"> <cfoutput> auth_key=#pusherKey# auth_timestamp=#authTimeStamp# auth_version=#authVersion# body_md5=#authMD5Body# name=#pusherEvent# </cfoutput> </cfsavecontent> <!--- Create the raw signature data. This is the HTTP method, the resource, and the alpha-ordered query string (non-URL-encoded values). ---> <cfset signatureData = ( ("POST" & chr( 10 )) & (pusherResource & chr( 10 )) & reReplace( trim( queryStringData ), "\s+", "&", "all" ) ) /> <!--- Create our secret key generator. This can create a secret key from a given byte array. Initialize it with the byte array version of our PushApp secret key and the algorithm we want to use to generate the secret key. ---> <cfset secretKeySpec = createObject( "java", "javax.crypto.spec.SecretKeySpec" ).init( toBinary( toBase64( pusherSecret ) ), "HmacSHA256" ) /> <!--- Create our MAC (Message Authentication Code) generator to encrypt the message data using the PusherApp shared secret key. ---> <cfset mac = createObject( "java", "javax.crypto.Mac" ) .getInstance( "HmacSHA256" ) /> <!--- Initialize the MAC instance using our secret key. ---> <cfset mac.init( secretKeySpec ) /> <!--- Complete the mac operation, encrypting the given secret key (that we created above). ---> <cfset encryptedBytes = mac.doFinal( signatureData.getBytes() ) /> <!--- Now that we have the encrypted data, we have to convert that data to a HEX-encoded string. We will use the big integer for this. ---> <cfset bigInt = createObject( "java", "java.math.BigInteger" ) .init( 1, encryptedBytes ) /> <!--- Convert the encrypted bytes to the HEX string. ---> <cfset secureSignature = bigInt.toString(16) /> <!--- Apparently, we need to make sure the signature is at least 32 characters long. As such, let's just left-pad with spaces and then replace with zeroes. ---> <cfset secureSignature = replace( lJustify( secureSignature, 32 ), " ", "0", "all" ) /> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <!--- Now that we have all the values we want to post, including our encrypted signature, we can post to the PusherApp REST web service using CFHTTP. ---> <cfhttp result="post" method="post" url="http://api.pusherapp.com#pusherResource#"> <!--- Alert the post resource that the value is coming through as JSON data. ---> <cfhttpparam type="header" name="content-type" value="application/json" /> <!--- Set the authorization parameters. ---> <cfhttpparam type="url" name="auth_version" value="#authVersion#" /> <cfhttpparam type="url" name="auth_key" value="#pusherKey#" /> <cfhttpparam type="url" name="auth_timestamp" value="#authTimeStamp#" /> <cfhttpparam type="url" name="body_md5" value="#authMD5Body#" /> <!--- Sent the name of the pusher event. ---> <cfhttpparam type="url" name="name" value="#pusherEvent#" /> <!--- Send the actual message (JSON data). ---> <cfhttpparam type="body" value="#pusherMessage#" /> <!--- Digitally sign the HTTP request. ---> <cfhttpparam type="url" name="auth_signature" value="#secureSignature#" /> </cfhttp> </cfif> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <cfoutput> <!DOCTYPE HTML> <html> <head> <title>PusherApp Hello World - ColdFusion Pusher</title> </head> <body> <h1> PusherApp Hello World - ColdFusion Pusher </h1> <form action="#cgi.script_name#" method="post"> <p> Enter your message to push: </p> <p> <input type="text" name="message" size="40" /> <input type="submit" value="Push It, Push It Real Good!" /> </p> </form> </body> </html> </cfoutput>
As you can see, the part that encrypts the message signature literally doubles the amount of code needed to get this example up and running. As I don't know much about cryptography (probably where my frustration really comes from), I had to borrow somewhat from Bradley Lambert's Pusher.cfc. Other than the encryption, however, this code really isn't doing all that much.
The HTML5-WebSocket-powered Pusher realtime client notification system seems very cool! I am sure that anyone who has built an application that has to continually poll the server for information will instantly see the value provided by this service. The only thing that I'm a little bit weary of is the fact that a critical part of my application might require the availability of a 3rd party system. Of course, the idea of being able to offload the management of such things is also super appealing. Plus, I believe in their FAQs, they mention wanting to use EC2 in the future to provide massive scaling and availability. In any case, this is something that I will definitely be playing around with a bit more.
Nice little example of Web Sockets Ben, I know a few months ago I was playing around with it but I was trying to get a local java version up and running on my machine with no luck in the end.
Interesting, will have to look into this. Yeah the crypto seems excessive, but for an app I am working on that will have a push component, that security actually will be useful.
I think too the notification service seems it might be meant to be small and compatible with iOS (based on what has been said about notification). So not a good way to send large messages.
Yeah, seems like it would be a pain to set up your own WebSocket server locally. It's funny, in the Pusher FAQs, they even address that question (as to why not just set something up locally):
You are very welcome to! From our experience it is a bit annoying to do this, and means you spend less time on making cool stuff.
I am not sure how much information can be pushed. Apparently, only iOS4 supports web sockets. I'll be playing around more with this.
Ben you should check out the webkitNotifications api for browsers. That is something I think will be really handy in the future.
I'm not familiar with that. I'll definitely have to check it out, thanks!
Thanks for the great post. Sorry the cryptography is a bit complex. We wanted to make sure you can trust that no-one other than you can send messages to browsers viewing your app. :)
There are several libraries available that hide this complexity. For example in the ruby library we maintain, after you set the app id, key and secret there is a single command to encrypt and post your message
Also, we are already set up and running fully on EC2, and are working on making our service reliable as good as possible.
No worries. As I mentioned above, I am sure that my frustrating stems more from the fact that I don't know much about cryptography than the fact that it is required. That said, what kind of security does signing add above simply requiring authentication with the HTTP request? I am not attacking - I literally don't know much about that theory.
Pusher app is very cool, if your doing any Ruby work, their gem does truly make it way to easy.
However for local home grown stuff, I have been looking at http://jwebsocket.org/. Being java, it is much more CF_friendly.
The main difference with HTTP Auth is how secure it actually is.
HTTP Basic is rather insecure, as it sends authentication in plain text.
HTTP Digest is more secure, but has some legacy issues. For example with some clients it is possible for a man in the middle to tell the client to downgrade to an older version of the auth protocol, where attacks have been discovered. This allows the attacker to get access to the user's credentials and effectively hack your account. I honestly can't speak to how many http libraries are vulnerable to this, as there are so many, and in so many different languages.
A third option would be HTTP Auth over SSL, which is secure, but adds quite a high percentage overhead when dealing with lots of small requests.
The ideal scenario (from our point of view) is to abstract the cryptography into libraries for each language, so that the application developer can ignore them.
Hope that helps :-)
Yeah, I've heard SSL adds a lot of overhead. I guess this makes the most sense. I am sure if ColdFusion had native support for the desired encryption algorithms, I wouldn't give it a second thought. I assume since I have to bring in so many Java libraries, I simply become much more aware of the effort.
Looks as if you should modify your "A Little Bit About Me" blurb to end with "using ColdFusion, jQuery and HTML 5".
Speaking of which, you may want to check out my plugins.jquery.com contributions so far: attrNameBegins and defineDatasets. I wrote them based on an e-mail conversation I had with Ian Hickson about how they should behave. They allow you to code source-code-compatible JS for HTML 5 datasets (data-* attributes) in advance of browser support. Of course, the HTML 5 implementation requires a getter/setter implementation that allows even deletion, so there physically isn't any way to do that with current JS. (Though I can't think of a reason why I would ever want to do such a thing.)
I also have an HTML 5 Web Forms 2.0 support matrix that I haven't published yet, if you're interested in that. One of the interesting things about Web Forms 2.0 on iOS is that if you use input type="search", Safari for iOS will pop up a keyboard with a Search key. Or input type="email" pops up a keyboard for e-mail addresses. Or input type="number" pops up a numeric keypad. Context sensitive keyboards.
HTML 5 is such a treasure trove of new features.
Yeah, this HTML5 stuff is super cool so far. I'm really enjoying it. I don't even have a really good sense of what HTML5 truly encompass; so, I'm just checking it out a little bit at a time. I'm sure it will be totally diluted the way "DHTML" and then later "AJAX" became. But, very cool stuff so far.
Your test app should strip all tags from the output, not just the ones that seem dangerous at first glance. The security-minded part of my brain can't get past my revulsion for XSS exploits and appreciate how useful it actually is. :(
I know it's just a hello-world testing rig, but secure code starts in the home, as they say.
I appreciate your comment; I'll make the htmlEditFormat() updates for my Pusher.cfc project. However, for any given post, there's only so much you can add before a post would be overwhelming (I think). I mean, technically, there should probably also be a try/catch around my CFHTTP request and some sort of reasonable timeout on it as well; but, like you said, it's just a "hello world" example.
That said, I happen to think realtime notification is a very exciting thing. If you come up with any good use cases on your end, I'd love to hear about them.
I have added htmlEditFormat() to my Pusher.cfc's online demo:
Again, thanks for the feedback.
jQuery 1.4.3 core includes the functionality of my .defineDatasets() plugin!
This is an own-horn-tooting followup to my Jun 28, 2010 at 4:24 PM post in this thread. jQuery 1.4.3 was just released today:
It contains a new behavior for the .data() method. If an HTML element specifies data-something="example" among its attributes, $(ref).data("something") will retrieve "example". In other words, they implemented it in the jQuery .data() way, not in the HTML5 way. But they recognized the need for it, and they implemented it! Yay!
I feel vindicated for recognizing that this HTML-to-JS communication feature was needed and writing the plugin to do it. So now if you want to do it the jQuery way, you can use the .data() method, or if you want to do it the HTML5 way, you can use my plugin. And my :attrNameBegins(prefix) selector can be used with either technique!
Woo and hoo!
Ha ha, awesome stuff :) Way to see the way of the future! The 1.4.3 release looks cool. At the jQuery Conference this last weekend, John Resig told us that they rebuild a lot of the CSS and attribute stuff to make it significantly faster. I love how they are constantly optimizing.
I have not been able to find much on running a NodeJS server/app in production.. ( all my playing around has been to just run node in a command prompt/terminal)
I can't imagine that is the best practice for production. Do you know how one run's a node app on a private Windows machine ? could it even be the CF server?