Space commitment changes

@bnewbold.net came back from his time off this summer and pointed out that the he didn’t think the currently specced commit signing scheme works how I hoped. To my great dismay & disappoint, I have to admit that he’s correct.

The problem

Basically, the current construction doesn’t give us the data-authentication guarantee we’re looking for.

We send three pieces of info:

  • the (signed) HMAC key
  • the repo hash
  • the HMAC

However, the HMAC is completely deterministically reproducible from the other two and is therefore redundant. The signed HMAC key is not irrelevant, but it essentially serves as a proof of possession of the signing key at write time. It doesn’t relate to the data at all.

We then considered an approach where the syncer provides the HMAC key or both sides contribute secret material that the HMAC key can be derived from. However, this relies on TLS for confidentiality. Anyone who can read the exchange can also derive the HMAC key and forge MACs.

This means that the HMAC just gives the same authenticity guarantee as the underlying transport. In other words, we don’t get any additional security compared to just doing TLS.

Option 1: Proof of possession

The first option is to just own up to this and do signatures for proof of possession.

Probably how this would look would be:

  • syncer generates a nonce & sends it on any authenticated sync method
  • repo host concatenates in the space uri & repo rev, hashes it, signs it

This has the same security as the current scheme (and is simpler).

However, we don’t authenticate the data so it isn’t very “Authenticated Transfer” of us.

I don’t personally see a great reason to do this.

Option 2: Shared secret

Really what we need is a shared secret. The tool for the job is a KEM (Key Encapsulation Mechanism).

KEMs let one side publish a public key and the other side can “encrypt to” that public key. In our case, a repo host would publish a public key and syncers would generate a shared secret and encrypt it for that repo host. They would send it on sync, and the repo host would compute the MAC using the shared secret.

btw if “KEM” sounds a bit unfamiliar: it means a mechanism that lets Alice establish a shared secret with Bob using Bob’s public key without Alice needing a public key of her one.

this is different from static-static ECDH where Alice & Bob can independently derive a shared secret just from their existing keys and each other’s public keys.

in the context of ECDH, it basically just means Alice generates an ephemeral key & sends the public key to Bob when she establishes the shared secret

this is meaningful because post-quantum crypto doesn’t have a good static-static solution (like ECDH), but it does have a KEM (ML-KEM)

The downside of this (and the reason we didn’t want to do it originally) is that you’re not supposed to use the same key material for ECDSA & ECDH. So this will entail adding a new public key to every account’s DID document.

This kinda sucks, but it is doable. And in terms of protocol changes that require a DID document update, atproto spaces seems like a big enough update to warrant it. We also may find other uses for “encrypt to a DID” over time.


I’ll write up a PR against the proposal for what this would look like. If anyone has any thoughts on the general shape, please chime in and let me know!

I’m also interested in discussing this at the spaces office hours this Thursday.

2 Likes

My short take is we should not be super afraid of updating DID documents at scale, at least when needed. We shouldn’t churn on it unnecessarily, but if it only happens rarely then the system/ecosystem will not be ready for when it does need to happen (eg, due to large PDS host security incidents, or in the near future when we do the transition to post-quantum cryptography).

There might also be other use-cases for DH/KEM in atproto down the line.

As a small brain-itch, for spaces specifically the requester is not identified by a DID (they just have a space credential), but their request is bound do a signing key (via HTTP Message Signature), which itself is bound to the space credential. That means we could do static-static key exchange if we wanted. But I think that causes problems for post-quantum so we shouldn’t do it.

Took a pass at this in the spec: 0016: Move space repo commit to a shared secret by dholms · Pull Request #122 · bluesky-social/proposals · GitHub

I also included some related changes that move the MAC into headers (as opposed to on the data) and offer MACs on getRecord requests as well.