Yesterday, I wrote a post outlining a draft specification for a possible way to handle login on a distributed social network, together with a reference implementation for Known.

I got some really positive feedback, including someone pointing out a potential replay vulnerability with the protocol as it stands.

I admit I had overlooked replay as an attack vector (oops!), but since peer review is exactly why open standards are more secure than propriatory standards, I thought I’d kick off the discussion now!

The Replay problem

Alice wants to see something that Bob has written, so logs in according to the protocol, however Eve is listening to the exchange and records the login. She then, later, sends the same data back to Bob. Bob sees the signature, sees that it is valid, and then logs Eve in as Alice.

Worse, Eve could send the same packet of data to Clare and David’s site as well, all without needing access to Alice’s key.

Eve needs to be able to intercept Alice’s login session, which, if HTTPS has been deployed is largely impractical, but since this can’t always be counted on I’d like to improve the protocol.

Countermeasures

Largely, countermeasures to a replay attack take the form of creating the signature over something non-repeatable and algorithmically verifiable that Alice can generate and Bob can check.

This may be some sort of algorithmically generated hash, a timestamp, or even just a random number, or record whether we have seen a specific signature before.

My specific implementation has an additional wrinkle in that it has to function over a distributed network, in which each node doesn’t necessarily talk to each other (so we can’t check whether we’ve seen a signature or random number before, since Bob might have seen it, but Clare and David won’t have).

I also want to avoid adding too much complexity, so I’d like to avoid, if I can, doing some sort of multi-stage handshaking; for example hitting an endpoint on the server to obtain a random session id, then signing that and sending it back. Basically, I’d still like to be able to talk to a server using Unix command line tools (gpg) and CURL if I can!

Proposed revision

Currently, when Alice logs in to Bob’s site, Alice signs their profile URL using her key and sends it to Bob. Bob then uses this profile url to verify that Alice is someone with access to Bob’s site/post and then users the signature to verify that it is indeed Alice who’s attempting to log in.

What I propose, is that in addition to forming the signature over Alice’s profile URL, she also forms it over the URL of the page she is trying to see, and also the current time in GMT.

Including the requested URL in the signature allows Bob to verify that the request is for access on his site. If Eve sent this packet to Clare or Dave, it could be easily discarded as invalid.

Adding the timestamp allows Bob to check that this isn’t an old packet being replayed back. Since any implementation should have a small tolerance (perhaps a few minutes either side) to allow for clock drift, using a timestamp allows a small window of attack where Eve could replay the login. To counter this, Bob’s implementation should remember, for a short while, timestamps received for Alice and if the same one is seen twice invalidate all of Alice’s sessions.

  • Why invalidate all of Alice’s sessions when we see the same timestamp twice, can’t we just assume that the second packet is Eve?”

    Sadly not – sophisticated attackers are able to attack from a position physically close to you, so Eve’s login may be received first. In the situation where two identical login requests are received, it is probably safer to treat both as invalid.

    Perhaps a sophisticated implementation could delay Alice’s first login for a few seconds (after verifying) to see if any duplicates are received, and only proceed if there are none. This would limit the need to permanently store timestamps against a user’s account, but may be more complex from an implementation point of view.

  • Why use a timestamp rather than a random number?

    I was going back and forth on this… a random number (nonce) would remove the vulnerability window, but it would require Bob’s site to store every number we’ve seen thus far, so I finally opted not to take this approach.

I’d be interested in your thoughts, so please, leave a comment!

Distributed social networks – tools that give you all the social and political benefits of the siloed networks (Google+, Facebook, etc), but without being a massive honey pot for surveillance and data mining, are, in my view, the way we should be heading.

In this model, public posts are easy (that’s just the web), but limiting posts so that they can only be seen by a limited number of your friends is somewhat harder. On Elgg, and similar systems, the standard solution was to make everyone create an account, and profile, on your node. This is, to a large extent, the traditional approach, but basically ends up with you having multiple profiles around the internet (with multiple passwords to remember) which are, crucially, controlled by a third party.

This is a bad thing, and in the post Snowden world, a downright dangerous thing.

I’ve previously discussed a possible approach to providing distributed signon using OpenPGP keys as identity mechanism, and I’ve finally got around to fleshing this out, and building a prototype, now that distributed friending is in Idno/Known core.

Protocol overview

  • Two user profiles, Alice and Bob
  • Alice and Bob generate, or otherwise associate, a PGP key pair with their users (for the most part, only public keys are used in this. You only need to store the private key on the server if you’re automating the process of signing in, and if you can store your private key in your browser, there is eventually no need to store private keys on the server).
  • Alice adds Bob as a friend, and Alice’s site visits Bob’s profile for his public key (see “Public key discovery” below)
  • Rinse, repeat, for Clare, Dave, Emma, Fred, etc…
  • Alice writes a post, and only wants Bob to see it. She lists Bob’s profile URL as an approved viewer.
  • Bob visits the private post, and identifies himself by signing his profile URL with his key, and then POSTing the ascii armoured signature as signature to the post URL.
  • Alice verifies the signature, and confirms that the key’s fingerprint belongs to Bob’s key, and if so, lets Bob see the post.

Public key discovery

Bob makes his public key available by putting it on his web server, and making it easily discoverable to Alice in one or more of the following ways:

  1. Via a HTTP Link header, with a rel of “key”, e.g. Link: https://example.com/bob/pubkey.asc; rel="key"
  2. Via a META tag in the HTTP header, e.g. <meta href="https://example.com/bob/pubkey.asc" />
  3. Via an anchor tag within the page body of rel=”key”, e.g. <a href="https://example.com/bob/pubkey.asc" rel="key">My Key</a>
  4. By pasting the key into the body of the page, and giving it a class of “key”, e.g.

<pre class="key">
-----BEGIN PGP PUBLIC KEY BLOCK-----
....
-----END PGP PUBLIC KEY BLOCK-----
</pre>

Identifying Bob

When Bob wants to see the post that Alice has made, he identifies himself by making a POST request to that page, containing a signed URL of his profile. Alice then verifies the profile URL against those she as allowed access, and verifies that the signature is both correct and that the fingerprint belongs to Bob’s key.

Alice may want to store these access details in a session so she can give Bob access to other resources (logging Bob in, in effect), but this is not strictly necessary.

Other methods are available…

So, why not use OAuth, or signed HTTP requests?

Well first of all, all these authentication methods are not mutually exclusive, so there’s no reason why you can’t use multiple techniques.

Second, we’re using very standard tools (GPG, POST requests, etc), and standard formats, bolted together. Meaning, among other things, although this example (and the Idno implementation) uses a website to do the signing in, this isn’t really required. You can sign in and see a private post, just as easily, using curl and gpg from the command line, if you so require.

Finally, this is entirely distributed, and unlike some implementations of Oauth, or even things like IndieAuth, it requires no central authority to vouch for you. Update:Aaron points out that the latest versions of Indieauth don’t require a central authority.

Idno reference implementation

I have written a plugin that implements this protocol for Idno. In addition to the basic spec, the Idno plugin has the following enhancements, which you may want to consider as well.

Firstly, it uses OpenPGP.js to generate the keypair on the client machine. This preserves server entropy, making it better for hosted environments.

Secondly, the plugin provides you with a bookmarklet, which makes signing in to a compatible site nothing more than a button click.

Please kick both the Idno implementation and the overall spec about, and let me know what you think!

» Visit the project on Github...