Hosted onhyper.mediavia theHypermedia Protocol

Zero-Knowledge Multi-Device Identity System

    Problem

      Last week in our gathering in Barcelona we discussed the problem of our current device linking workflow. It's super hard for users to have a coherent web experience without having to jump through many hoops, all of which are tied to the desktop app, which many of our users don't even have (especially the new ones).

      Users end up creating scattered identities on different HM sites they visit, without completing the key linking flow, which ultimately leads to losing the identity at some point in future, because browsers are not careful with keeping those keys around.

      We need an easy ramp up for new users to create an identity on the web, without compromising sovereignty, security, and convenience (must support seamless multi-device without desktop app).

      1

      The solution we landed on last week (described roughly in New Identity Execution Plan) could work, but it has some compromises on sovereignty, requires a certain level of trust on the server-side infrastructure, and introduces some new concepts on the permanent data layer (mainly the notion of a user ID) which we haven't fleshed out fully yet.

      1

      The solution described in this document aims to make no compromise on sovereignty, improve security, minimize the level of trust in the server to the absolute minimum, and avoid introducing new concepts into our permanent data model. All for the very slight compromise on convenience — no magic links via email — although some people (myself included) would take it as an advantage, because a lot of people hate magic links for every log in.

    Scope

      2-3 weeks. I have some prototypes which are already in a fairly advanced stage, but full integration into sites and desktop could take a while, considering the UX and the design work.

    Solution

      A zero-knowledge (E2E encrypted) identity service with a security model and architecture similar to 1Password, Bitwarden, Proton, Signal, Keybase, and other similar privacy-focused systems — the server never sees user secrets, and is almost oblivious to what users do.

      The core idea is simple:

        We run an identity service somewhere on our infra.

        Users create an account with an email on that ID service. Unfortunately we can't do magic-links here — we need a password, or a passkey (or both if they want). Fear not, the UX can be very sleek for most people who have modern devices, and for those who don't — even the password flow can be a smooth experience if we're careful with providing good HTML that interoperates nicely with browsers and password managers.

        1

        We verify the email, and then allow the user to create a "Vault" (or a Wallet I guess).

        For now the Vault would only hold user's HM Account private keys (we can allow multiple keys, just like in the desktop app), plus some secret device/session metadata. Eventually we can use this storage for subscriptions, read/unread status, and other cool things.

        The user generates a secure random symmetric Data Encryption Key (DEK) in the browser. This key is used for encrypting the data in the Vault, and is never exposed anywhere beyond the user's device. It's never persisted anywhere in clear text, and is only stored in-memory for the brief moments that user interacts with the Vault.

        The DEK is further encrypted with a separate symmetric Key Encryption Key (KEK), which is either securely derived from the password, or passkey. The DEK encrypted by the KEK is stored on our server along with the vault data.

        Users are able to access the Vault at any time with their credentials (email + password or passkey).

        When users visit HM sites they create session keys to interact with them. Then they have the option to link the session key to their account by interacting with their vault. Just like we do with desktop app today, but much more convenient. We can keep track of the metadata about each session encrypted in the vault as well, helping users review and revoke their sessions later if they want.

      Note the separation between DEK and KEK — it's a common practice in all those privacy-focused services I mentioned earlier. This nomenclature is also common. This separation allows us having clear boundaries between identity/account, session, and credential:

        Account Key — identity / account.

        Session Key — temporary permission to act on behalf of the account on HM sites.

        DEK — extra layer of protection for the account.

        Email + password or passkey — credential for accessing the account. There can be many credentials for the same account, depending on the number of devices, and user's paranoia. Users can change their credentials, designate other people for social recovery, have different passkeys on multiple devices, create paper keys (like Keybase), and so on.

      Why This Is Different from Anything We've Talked Before

        All our previous attempts at solving the device linking problem revolved around some of these ideas:

          Abusing passkeys for storing user's Account keys. The WebAuthn spec allows storing small amounts of data in the user.id field of the passkey, but relying on it as the primary storage for user's identity just feels wrong, because it's not designed for it.

          Using passkeys themselves as Account keys. Passkeys are authentication credentials, not identity credentials. You can prove you're the same person who registered, but you can't (or shouldn't) use a passkey to sign arbitrary data (like HM documents).

          Sacrificing security by letting the server sign user's data (Bluesky mode). This is simple to implement, but completely breaks the sovereignty model. The server becomes a trusted party that could impersonate any user. A server breach means game over for every user. We'd be building a centralized identity system with a decentralized data layer — the worst of both worlds.

          Asking users to type in their mnemonics on the web. Mnemonics are designed for disaster recovery, not daily use. Typing 12/24 words into a browser is error-prone, phishing-friendly, and terrible UX. Most users would give up or store the mnemonic somewhere insecure (clipboard, password manager without seed phrase support, screenshots).

          Downloading recovery files. This offloads the problem to the user's file system. Where do they store it? iCloud? Google Drive? A USB stick they'll lose? Users don't have good answers for secure file storage, and those who do probably don't need our help. Recovery files also don't solve the multi-device problem — they're a backup mechanism, not an access mechanism.

          Relying too much on our server, sacrificing sovereignty. The common thread in several previous proposals was having the server mediate identity verification in ways that gave it implicit control over it. Users would need to trust our infrastructure in ways that undermine the sovereignty promise, making a "convenient" identity setup very different from a "sovereign" identity setup.

          1

        This new approach makes the convenient identity work exactly the same as the sovereign one — even allowing users to migrate back and forth easily.

        1

        It still requires some level of trust — that we won't lose your data, and we'll serve you the correct JavaScript code in the browser, but this is a common theme in all the privacy-focused systems I mentioned before, and it seems like a fair price to pay for the convenience. In the end, all of this is totally optional — you could self-host this identity service yourself, or just use the app.

        2

      Cryptography Summary

        Account Key (Pair): Ed25519, or ECDSA — no changes here. These keys will be stored in the vault, or in the desktop/mobile app.

        Session Key (Pair): Web Crypto keys, unextractable from the browser — no changes here. These keys are only stored in the browser, and are authorized by the Account keys to act on behalf of the Account.

        Data Encryption Key (DEK): symmetric key for encrypting the Vault. Stored on the server encrypted by KEKs.

        Key Encryption Key (KEK): one or many symmetric keys stored as Passkeys, or derived from user's master password (which itself could be stored in a password manager — something we should recommend actually).

      Site Authentication Flow

        TODO

    Rabbit Holes

      Passkeys

        Initially I thought about using the user.id field of the passkey to store the encryption key (KEK) — i.e. abusing the system just like we've been saying before. Still, this is much less worrying — because it's just a credential (maybe one of many), not the account itself.

        Yet, I feel a bit uneasy about abusing passkeys this way. It seems to work fine on Apple and Google platforms at least, but other browsers and OSs may be doing something weird with the user.id field, and maybe could lose it, who knows.

        The WebAuthn PRF extension which allows deriving symmetric keys given some salt seems becoming more widespread these days. Maybe we should use that, at least when it's available.

      Passwords

        We can't reset passwords in this system. We can let them change it if they know it, but they must not forget their password, unless they have additional credentials configured.

        1

        Passwords also need to be quite secure, because their are used in a key derivation function. We should offer a good strength estimation for passwords, maybe using something like zxcvbn.

        1

        Ideally we should recommend using a password manager. If we are careful with our HTML and test it thoroughly (I have a few experiments), it should be pretty easy for most users — luckily these days your phone almost throws you a package manager in the face — even my parents use it.

        Maybe we should always require passwords, and offer passkeys as an additional convenience credential.