Thursday, September 22, 2016

Asymmetrical Signing with Google Macaroons

I'm a big fan of Macaroons but it's always bugged me a bit that if you want separate issuing/consuming services you need a shared secret between them.  Main reason I didn't like this is that the consuming service then had the power to create new macaroons with different permissions. Of course the consuming service doesn't exactly need permission to access/modify its own resources so it's really only an issue if you don't use different shared secrets for each service.  It also makes it a rather big pain to rotate secrets as you have to update it in multiple places.

One of the things I really like about signed JWTs is that anyone can safely verify the token without accidentally allowing people to create new ones.  So i've been pondering the idea of public private keys and macaroons for a while trying to figure out if they can work together nicely.

Not sure how useful this is, but I haven't seen anyone else write about this topic (which could be a bad sign for security) so thought i'd do a brain dump.

Disclaimer: The following was a shower thought and has not been particularly well explored so it might not be particularly secure (I'm a security enthusiast but not an expert,  I have revised this post at least once fixing a couple of silly mistakes.)

Standard Method = Token + Secret => Hash then send the Token + Hash to relying party

To verify the token you need to know the Secret.

Adding PPK is really quite simple, you simply encrypt the secret + hash used as part of the macaroon signature using your private key and include the result as part of the token. This means anyone with the public key can verify the tokens authenticity and then that the chain afterwards is valid.

Potential PPK Method = Token + Secret => Hash then send Token + Hash + Encrypted Secret to relying party  - T + S => H, send T:H:E(S)

To verify the token you first obtain the secret by decrypting it, then follow the standard Macaroon verification.

Chaining after this point works the same as previously, the old hash is used as the secret for the new token.  T + T1 + OldHash => New Hash, then send T + T1 + NewHash + Original Encrypted Secret.

However, this isn't quite enough, anyone with the decryption key could change the token, create their own hash with the secret they decrypted and pass on the old encrypted secret with the message. For this reason the original hash also needs to be included in the encryption for verification.  T:H:E(SH) or T:T1:H1:E(SH) for a chained message.  It also needs to be included decrypted so that chaining works (can't chain without the previous hash).

It does mean anyone with the public key can remove constraints that were added after the original server by rebuilding the entire chain without the offending parts, so you still don't want to give the public key to just anyone. But that's a limitation with Macaroons in the first place, the secret key is secret for a reason.

I believe it still allows constraints to be added to the token by anyone it passes through, which still makes it more powerful than standard JWT PPK verification.

What it appears to allow is the secret to be rotated or even randomized completely, that might not be a major benefit when you consider you have a new secret (being the 'public' decryption key) which is still hard to rotate as it's not really public (and could introduce problems due to people treating it too publically) but actually just asymmetrical encryption with different secrets at the issuer / consumer.

So overall I think it's an improvement over standard Macaroons but still doesn't solve everything I was hoping for.

  • Consuming services can't create new tokens
  • Still can't easily rotate keys
But maybe something like the ratcheting from Whisper could be used to improve that (https://whispersystems.org/blog/advanced-ratcheting/)

Dynamic Navigation via Claims-based Dependency Injection (website composition)

It's not uncommon for websites to show/hide navigation based on what roles a user has.  This become more problematic when the links being shown/hidden are links to other websites.  Each site then needs to know about the others and any time you deploy a new site as part of the suite you need to update each app to add it, and maintain the logic based on roles for which items to show hide.  A solution i've seen for this is to create a webservice and ask it what nav you should be showing, I'm not a huge fan of pull based services so this has always bugged me.

I am however a big fan of Dependency Injection and while this issue isn't exactly all that related, it occurred to me that your login portal (WS-Fed, OAuth or whatever) can kind of be used like a DI Container to inject things into dependent apps.  The change is extremely simple, instead of the IdP (just) telling apps what roles the user has, they also pass through a list of navigation items to display for that user.  The nav then becomes a fairly dumb component that just reads the claims passed in and shows them.  Thus centralising all the logic around what to show to what user, and considering the IdP normally has a list of relying parties anyway it's not exactly new information to it.  Of course it doesn't really work for internal links or links to systems not controlled by the IdP.

This is great when different roles have a different subset of apps they have access to, you can use this technique to create modular user portals that reuse common functionality/apps but still make it feel like a single portal (as long as look and feel is common).  Where it becomes really neat is when you start looking into beta testing / A/B testing / slow rollouts where you can replace entire applications for specific user groups with a newer version instead of a big bang approach.  Again not really suitable for in app features (especially if you have multiple different toggles) but for that you can fall back to roles.