storacha-network / specs

πŸ… Technical specifications for the w3up protocol stack
17 stars 0 forks source link

did snapshots in UCANs #13

Closed Gozala closed 1 year ago

Gozala commented 1 year ago

Pulling the thread out of https://github.com/web3-storage/specs/pull/12#discussion_r1033958400 to continue further discussion.

Pasting comment from @expede below

Sorry for the post-merge feedback @Gozala !

wrt send/message, that's new right? I notice that it is of the form verb/noun whereas most of the time our caps name convention is more like noun/verb

@gobengo I've been thinking about this in a UCAN "standard library" as msg/send, which cuts across email, text messaging, message passing, etc etc.

iss: "did:dns:web3.storage", // <- Maybe this should be did:key:root here
aud: "did:dns:web3.storage",

I continue to find iss == aud deeply strange. I agree with your comment "Maybe this should be did:key:root here".

Rather than granting the ability to sign "as" another DID, I think it's cleaner to make this a "forwarding edge", which follows much more the capabilities worldview. I think that the end result is the same, so it's just naming.

Time 1: carol --[fwd_all]--> dan
Time 2: alice --[can: foo]--> bob --[can: foo]--> carol
Time 3: dan --[can: foo]--> frank

BECAUSE we can substitute a link:

alice --[can: foo]--> bob --[can: foo]--> carol
                                            |
                                        [fwd_all]
                                            |
                                            V
                                           dan --[can: foo]--> frank

End result: Frank can use Alice's foo

This is kind of like "signing as" Carol, but it's only at the level of authority, not identity. Alice saying "you can sign as Bob" is going to get really confusing unless you want to do DID management, which IMO should be at a DID layer. I think you can manage all of this cleanly at the layer of capabilities.

This is a powerful enough feature that has enough uses that I propose that we add this as a first-class feature to UCAN at some stage. In the meantime, I propose that we call this something other than "signing as".

Here's some delegations in this model:

w3s --[fwd_all]--> didJune
w3s --[fwd_all]--> didJuly
w3s --[fwd_all]--> didAug

## Super simple case, nothing unusual ##

w3s --[fwd_all]--> didJune --[w3s's foo]--> alice

## More complex case with new forwarding concept ##

bob --[bob's bar]--> w3s
                      |
                   [fwd_all]
                      |
                      V
                   didJune --[bob's bar]--> carol

Here's that last one concretely:

{
  iss: "did:key:june",
  aud: "did:key:carol",
  att: [{ can: "thing/doSomthing", with: "resource://example.com" }],
  sig: "...",
  prf: [
    {
      iss: "did:key:zBob",
      aud: "did:dns:web3.storage",
      att: [{
        with: "resource://exmaple.com",
        can: "thing/doSomething"
      }],
      exp: august2022,
      sig: "..."
    },
    {
      iss: "did:dns:web3.storage",
      aud: "did:key:june",
      att: [{
        with: "did:dns:web3.storage",
        can: "ucan/forward_all" // we can come up with a better label
      }],
      exp: november2022,
      sig: "..."
    }
  ]
}
Gozala commented 1 year ago

Thanks for the feedback @expede and you are right my example there is better served by using forwarding than trying to shoe horn ucan/sign thing there. I did however conflated two different use cases and solutions.

Let me try and explain this other use case, which is what PR was about:

  1. We would like to allow users to link bunch of resources (identified by did:key to the user account identified by did:mailto).
  2. For example when Alice creates a new "space" she can delegate access to it to her account
    did:key:zSpace --[can:* with:did:key:zSpace]--> did:mailto:alice@web.mail
  3. Alice on new device wants to get capabilities delegated to her account
     did:mailto:alice@web.mail -- [can:*, with:*] --> did:key:zAgent

Assuming we implement the forwarding Alice would be able to do thin on a new device:

did:key:zSpace --[can:* with:did:key:zSpace]--> did:mailto:alice@web.mail --
                                                                           |
----------------------------------------------------------------------------
|
-> did:mailto:alice@web.mail -- [can:*, with:*] --> did:key:zAgent --
                                                                    |
---------------------------------------------------------------------
|
-> did:key:zAgent -- [can:upload/list, with:did:key:zSpace] --> did:dns:web3.storage

However we still need to somehow create this delegation

did:mailto:alice@web.mail -- [can:*, with:*] --> did:key:zAgent

One option would be to:

  1. Create some DID document corresponding to did:mailto:alice@web.mail
  2. Define protocol for publishing and resolving did:mailto documents
  3. Use the above to create delegation from did:mailto:alice@web.mail to did:key:zAgent

While above option is certainly possible, I think it has ton of tradeoffs and requires out of the bound state sync and checks. I think we can do better by doing following:

  1. Instead of having did:mailto documents and update their state we could capture it's state in UCAN proofs (or perhaps facts).
  2. We could do it even better with DKIM, but for now we could do something simpler.
  3. We could certain authority to delegate us { can: "ucan/sign", with: did:key:authority, nb: { as: "did:key:zAgent" } } capability after verifying that request is from Alice.
    • Delegation means it can be revoked, it has expiry and all the other good stuff.
  4. We could embed such a proof in our UCAN which can act as DID document snapshot signed off by authority.
  5. We could use above proof when talking to the authority as proof that did:mailto:alice@web.mail has key did:key:zAgent (unless delegation has expired or has been revoked)

All in all did:mailto:alice@web.mail -- [can:*, with:*] --> did:key:zAgent this would look as a following UCAN

{
  iss: "did:mailto:alice@web.mail",
  aud: "did:key:zAgent",
  att: [{ can: "*", with: "*" }],
  prf: [{
     iss: "did:dns:web3.storage",
     aud: "did:mailto@alice@web.mail",
     att: [{ can: "ucan/sign", with: "did:dns:web3.storage", nb: { as: "did:key:zAgent" }  }],
     exp: future,
     sig: "..."
  }],
  exp: future,
  sig: "..."
}

If did:dns:web3.storage encounters ☝️ proof somewhere in the chain, it can resolve key for did:mailto:alice@web.mail from the proof it issued to did:key:zAgent and if signatures match, and non of it was revoked or expired it can consider this delegation valid.

It is worth pointing out that this is not tied to did:mailto, I think it can be used across all the DIDs that require some resolution scheme, it simply allows resolutions state to be captured inside the UCAN. It also means that even if keys in DID document got rotated there is a proof that some key was in there at the time when authority performed resolution.

Tradeoffs

It is true that DID document may update keys sooner than embedded snapshot expires. It is by design eventually consistent and authority is free to choose time window where drifts could be tolerated. Authority may also choose to perform DID resolution even if it is the snapshot is in the proofs, this merely offers a way to operate under conditions where actual resolution may be impractical.

Gozala commented 1 year ago

Alternatively authority could delegate (or rather invoke) capability for updating local execution state. In the context of specific UCAN validation below proof would update local DID documents state, specifically merging keys into state.documents["did:mailto@alice@web.mail"]

{
   iss: "did:dns:web3.storage",
   aud: "did:mailto@alice@web.mail",
   att: [{
      can: "did/update", //  <-- instruction to update local state of DID doc with id corresponding to `aud`.
      with: "did:dns:web3.storage",  // <-- authorization scope
      nb: {
        // basically stuff from https://www.w3.org/TR/did-core/#verification-method-properties
        "keys-1": { // <- key id
            "type": "Ed25519VerificationKey2020", 
             "controller": "did:example:pqrstuvwxyz0987654321",
             "publicKeyMultibase": "zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
        }
      } 
   }],
   exp: future,
   sig: "..."
}
Gozala commented 1 year ago

It recently occurred to me that this did key association is essentially equivalent to authentication cookies in browsers.

On login server sets some cookies which agent will send with every request allowing server to identify a user keep and keep them logged in.

Conceptually we do basically the same but with PKI. User authenticates with user DID + agent did:key. Authority (that performed verification) hands back signed delegation which just like cookie has expiry and when transmitted back can recognize user agent and keep them logged in.

As mdn puts it about cookies

It remembers stateful information for the stateless HTTP protocol.

It is exactly that stores stateful information inside UCAN to make them stateless & consequently offline friendly.

This framing clarifies some things that I had hard time articulating:

  1. No DID updates are intended here, just authentication of did identified user with a did:key identified agent.
  2. Agent is not given any permanent authority over user did, it simply mediates user access.
  3. User did β†’ Agent did mapping is local to UCAN invocations that include them.
  4. Authority can revoke delegation just like service can log out user by revoking set cookie validity.
Gozala commented 1 year ago

In last iteration of this idea I have changed it to look like:

{
   iss: "did:web:web3.storage",
   aud: "did:mailto@alice@web.mail",
   att: [{
      can: "did/update", //  <-- instruction to update local state of DID doc with id corresponding to `aud`.
      with: "did:web:web3.storage",  // <-- authorization scope
      nb: {
        // keys to add remove to the identifier
        "did:key:zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV": true
      }
   }],
   exp: future,
   sig: "..."
}
Gozala commented 1 year ago

I have also started to realize that DIDs are really getting in the way here. In fact we don't need DIDs here we need petnames and ucan delegations basically create edge names

Specifically "did:web:web3.storage" says I assign did:key:zH3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV petname "did:mailto@alice@web.mail". It's ok to assign other keys same petnames just like it's ok for same contact in my address book to have several phone numbers and email addresses.

Partner service could even use edge names to refer to them by surfacing relation that "web3.storage" calls this key a "alice@web.mail"

Gozala commented 1 year ago

@gobengo I'm starting to think that maybe indeed not using did's in ucan principals would be more beneficial than trying to map local state onto DIDs which seem to imply that all the keys must be canonically resolvable

gobengo commented 1 year ago

@Gozala it seems like just using petnames would mean that whenever we need to rotate our root key (even if it is rare), all the clients would have to also update their petname registry to know about the new key. If we use a canonically resolvable DID, then clients could re-resolve our did to know about the new intended keys. That way we can rotate our root key and all clients can run the same software/config as before, they just re-resolve our did.

Gozala commented 1 year ago

@Gozala it seems like just using petnames would mean that whenever we need to rotate our root key (even if it is rare), all the clients would have to also update their petname registry to know about the new key.

I'm not sure I understand the problem here. Yes if we rotated our key and did:web:web3.storage no longer contains key that signed the petname assignment you can no longer trust it because someone may have impersonated us.

If we use a canonically resolvable DID, then clients could re-resolve our did to know about the new intended keys. That way we can rotate our root key and all clients can run the same software/config as before, they just re-resolve our did.

I'm not sure I understand how this relates to above thread. It's not about us using canonically resolvable id vs not, it's about do our users need to have canonically resolvable DIDs or can we just give them petnames instead ? My argument with canonically resolvable things is that it's a lot of infrastructure requirements and perhaps in many cases it's unnecessary overhead and complexity that can crumble in bad network conditions.

expede commented 1 year ago

maybe indeed not using did's in ucan principals would be more beneficial than trying to map local state onto DIDs which seem to imply that all the keys must be canonically resolvable

Can you expand on this? Do you mean only using did:key rather than did:dns for your top-level key?

expede commented 1 year ago

This framing clarifies some things that I had hard time articulating:

FWIW I like the whole comment where this appears β˜οΈπŸ’―πŸ‘

expede commented 1 year ago

basically create edge names

@gobengo perhaps I can help clarify a bit, because I also found this confusing until a recent conversation with @blaine who clarified that edgenames are possibly the more interesting thing. Christine's text is very petname-centric, but if you're going from petnames "up" to the rest of the world, or rest of world "down" to petnames, you need to cross through edgenames, which are verifiable via (e.g.) DKIM, DNS, etc.

expede commented 1 year ago

Thanks for the feedback @expede and you are right my example there is better served by using forwarding than trying to shoe horn ucan/sign thing there. I did however conflated two different use cases and solutions.

Ooooh okay I think I see how you're thinking about this now. I was reading the previous post as focused on "who" ("sign as" this DID), rather than on the "what" (capabilities flowing "though" aligned iss/aud pairs). I definitely agree with the forwarding idea, which I think is just a naming thing in the can at this point, so while naming is important, I think we're aligned on the actual mechanism! πŸŽ‰

blaine commented 1 year ago

I'm not sure I understand how this relates to above thread. It's not about us using canonically resolvable id vs not, it's about do our users need to have canonically resolvable DIDs or can we just give them petnames instead ? My argument with canonically resolvable things is that it's a lot of infrastructure requirements and perhaps in many cases it's unnecessary overhead and complexity that can crumble in bad network conditions.

Definitely having petnames is great, but I think they're best thought of as a convenience mechanism, not as something to rely on.

Passkeys are great, because they enable us to abstract away the username, but they're also terrible because they 100% lock the user in to Google or Apple's infrastructure. The synchronization provided by google and apple is key, because passkeys without a synchronization mechanism would be terrible for users. Without it, a new phone => "Who dis?" everywhere on the internet.

Similarly, petname <=> edgename mapping works great in a p2p context if you have a link established to transport the names and mappings, but in many cases where this is useful, we don't have those. A petname in your phonebook isn't useful without a routeable edgename to map it to (e.g., that "Barack Obama" entry in my phone's contact db isn't very useful for actually calling Obama).

So, there are two (broad) use-cases here:

  1. edge-name to routing mapping; given an edge-name, how do I route to the entity referred to it, possibly over a protocol that the edge-name wasn't designed for? (my go-to here is that I'd love to be able to map my gmail address to e.g. activitypub, even though Google doesn't have a webfinger endpoint).
  2. name to name mapping: given a name (edge, pet, key), we often want to be able to map to another name (again: edge, pet, key).

One motivating use-case with direct applicability to the authn question:

Assume I have access to w3storage, enabled by the WhoCAN (I'm going to keep calling them this πŸ˜…) delegation that @Gozala has described here. I've delegated to a passkey, which is great – until I need to access my data from a public terminal. Now I'm stuck – I don't have access to my passkeys (phone battery died, lost/stolen phone, etc), and I'm in a situation where I don't really want to configure the computer I'm using with full passkey access.

So, instead, I type in my email address into the w3storage sign-in page, which can do the following:

  1. Look up my email address in the edgename discovery system (more to follow on this πŸ‘‡), and they find:
    • a valid delegated key and
    • a valid authentication mechanism.
  2. They first prompt me to present (a ucan signed by) my key. I can't do that, so instead they:
  3. Present the authentication mechanism listed in my edge-name profile; they send me to the appropriate endpoint, and I sign in to the site (which provides custodial auth) and I'm issued with a UCAN that's sent back, OAuth style, to w3storage.
  4. The alternative here is that w3storage could just send me an email with an authentication code, but that relies on knowing that email is a valid routing for my address, and that I can access it (not necessarily true for Mastodon accounts, or in China, for example).

In most cases, because the (obvious, but almost never used) MX lookup to determine better auth options isn't obvious, we don't use it. Using passkeys alone is great, but just hides the complexity in a complicated way that is more likely to break, especially in edge cases.

There are a bunch of other use-cases; but this comment is probably already long enough! Anyhow, I've been working on a spec for what we're calling NNS, the Name Name System:

https://talk.fission.codes/t/nns-the-name-name-system/3684

I would love it if this delegation stuff would work for NNS (in fact, I depend heavily on it!) πŸ˜