@icidasset and I were discussing some aspects of our new server implementation, and hit upon this totally random idea that might be totally obvious and already implemented all over the place, but I haven’t seen it before so writing it down here.
NB, this is obviously just “Insecure PAKE” BUT really really simple and usable in place of “we emailed you a short opaque code.” I have to assume that PAKE isn’t widely used for this because having a key is meaningless unless you’re signing something. We have UCANs, so it becomes meaningful?
For proof-of-identity, our first version is going to use the tried-and-true email challenge / magic link approach: your account has an email address associated with it, we generate a random code, send you an email with that code, you give us back that code proving that you have access to the email inbox, we give you access to your account. Viola!
We were discussing this in the context of did:odd, because did:odd is effectively the same thing, where “did:odd” means “your trust root is our request token database,” and it occurred to us that instead of sending a temporary token, we could just send the full ed25519 key, since they’re short enough. BUT, they’re still kinda long and weird to read.
SO - and this is not particularly surprising - what if instead of sending the full private key, we do something in between:
First, the user’s agent makes a request to reset the account:
POST /auth/requestToken?account=alice@example.com
We generate a new ed25519 key, zAlice
, and a short (4-6 digits) random token, tokAlice
. We divide zAlice
by tokAlice
to get partAlice
:
partAlice = zAlice / tokAlice
(obviously zAlice
needs to be divisible by tokAlice
; how to generate the two numbers so that that’s true is an exercise left to the reader. Alternate approaches that rely on encoding remainders into tokAlice
is left as an exercise to the reader)
We then return partAlice
to the user agent, which will effectively be a client key. Next, we send Alice tokAlice
by email, to alice@example.com. Alice receives the 4-6 digit token, which she provides to her user agent.
The user agent multiplies tokAlice
by partAlice
and now has possession of the full private key:
zAlice = tokAlice * partAlice
which can then be used to sign a UCAN using did:key:zAlice
– this can be used to make the request to update Alice’s account. zAlice
is only temporarily stored on the server, and has a short expiry associated with it in our database to prevent misuse in case partAlice
or tokAlice
aren’t removed from Alice’s computer or intermediate systems.
This approach has the following advantages over traditional magic link schemes:
- We can use a full private key to sign the UCAN, but with the UX benefit that Alice only needs to interact with a short token.
- The token emailed to Alice is only usable by a specific user-agent. Anyone who intercepts
tokAlice
is unable to use it without also compromising Alice’s user agent. - The client token
partAlice
is useless withouttokAlice
. Obviously,tokAlice
could be brute-forced, but the combination of a limited lifetime forzAlice
plus rate limiting means that we can prevent brute-force attacks. - It’s freaking trivial to implement. But maybe that makes it ridiculously insecure? How insecure is it relative to emailing an opaque token, though?
Questions:
We could of course use a better PAKE scheme (OPAQUE, CPace as approved by the IETF), or even just the token directly as a key, but all of the former options I’m aware of are designed for securing passwords that are intended to be used long-term and are therefore much more complex, and doing the latter increases complexity without meaningfully improving security. But, maybe that doesn’t matter?