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/)