Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UCAN and resources path discussion #2

Closed
wants to merge 1 commit into from
Closed

Conversation

hugomrdias
Copy link
Contributor

@adamalton and @flea89 made a great doc outlining a bunch concerns around UCAN, resource path and DID.

The main topic here is that we couple the concept of root bucket with the user DID storage://<user-did> but it also goes deeper into others issues.

Please give it a read here https://hackmd.io/@adamalton/ByUEKhv-c and provide feedback please.

I already left some inline comments and also extracted some issues that we should work on.

  • When a user registers a DID, he is effectively reserving a namespace ( bucket key name ) so for this reason we might need to start requiring proof of ownership upon registration to avoid squatting. Right now we dont require this because if the user doesnt own the private key he cannot create a request UCAN for our service, so its safe.
  • For us to be able to allow delegated users to do any operation in the UI, we need to have the upload associated with the UCAN that uploaded it, and this user needs to prove he owns the private key for that UCAN in the UI to "unlock" things like list files etc. But that upload UCAN needs to include any future operations the user may do in UI.
  • Revocation needs a lot more research
  • Recovery is tricky because the bucket name is the DID, so we cant just allow the use to change to another DID and have access to the lost DID bucket.

/cc @mikeal @Gozala

Use the user’s public key as the bucket name, but don’t store it in the DB.

This is pretty much un-viable because:
* If we don’t store it in the DB then we can’t make it unique to the user. So each time a user created a UCAN they could do so with a different public key, giving them (yet another) new bucket.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is intended behavior. There should be no 1:1 correspondence between user and places it can write to. It's an n:m correspondence, user may have access to multiple places and multiple users might have access to the shared places.

This is pretty much un-viable because:
* If we don’t store it in the DB then we can’t make it unique to the user. So each time a user created a UCAN they could do so with a different public key, giving them (yet another) new bucket.
* We have no way of knowing which bucket belongs to which user; we only know which bucket belongs to which key. This creates a situation with several distinct downsides:
- a. If the user loses their private key, they lose access to their bucket; we can’t implement any form of recovery.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think this is accurate. Remember service gives user access to the specific namespace. Service can revoke that access and give out another token for the same namespace. Of course giving access to random actors to the specific namespaces would be undesired. However if user can credibly proof that they should have access to specific namespace(s) there is no good reason to deny issuing them another token. Analogy here could be if I lost a movie theater ticket, I might show up with purchase receipt or ID and ask theater to reprint a same ticket so I could be admitted.

In our case we can implement recovery either in our web UI allowing user to add another DID to the specific namespace (bucket). Once that occurs client could request a UCAN from us for the specific DID and namespace(s), which our service could grant. There could be alternative mechanisms to do this, but main point here is in order to recover service needs to verify user is who they claim to be, with that issuing another UCAN is fairly trivial.

You can also think about as an equivalent of user binding another keypair to an account, except retroactively.

* If we don’t store it in the DB then we can’t make it unique to the user. So each time a user created a UCAN they could do so with a different public key, giving them (yet another) new bucket.
* We have no way of knowing which bucket belongs to which user; we only know which bucket belongs to which key. This creates a situation with several distinct downsides:
- a. If the user loses their private key, they lose access to their bucket; we can’t implement any form of recovery.
- b. If the user’s private key is leaked, then they have no way of revoking it, so access to their bucket is permanently breached.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me preface with saying yes revocation is not easy with UCANs, but it is not impossible. Best line of defense here is to issue UCANs with an experity date that strikes balance between needing to rotate & amount of damage attacker can do in that time frame. Ultimately this balance is not ours to strike as users themself will be in better position to do so.

I think assumption here is that if attacker obtains a private key, it permanently gains access to the namespace (bucket) because it can re-issue new UCAN for it. This is inaccurate.

If attacker gets hold of a private key, it can request an new UCAN authorization from service for corresponding namespace bucket. However service is still in a position to deny such request, which it will once it becomes aware that key was compromised. Furthermore service can revoke all the UCANs it issued for the given DID when it becomes aware of the key been compromised and subsequently deny service to request with those UCANs and all the UCANs that were delegated from it.

Again this is not to say this is piece of cake, but it is not too different from compromised password. Service somehow needs to be made aware that has occurred, somehow verify the user (usually via emal link) and let them create a new password. Our workflow could be the same except instead of user typing new password, they would paste new public key / did.

* We have no way of knowing which bucket belongs to which user; we only know which bucket belongs to which key. This creates a situation with several distinct downsides:
- a. If the user loses their private key, they lose access to their bucket; we can’t implement any form of recovery.
- b. If the user’s private key is leaked, then they have no way of revoking it, so access to their bucket is permanently breached.
- c. The user could potentially create “backup” keys by creating UCANs with unlimited expiry times which delegate full privileges to another key. This helps mitigate problem (a), but makes problem (b) more likely.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad idea. As alluded to earlier we should not even issue UCANs without expiry date as that is primary line of defense.

That said, it is accurate that user can (and probably should) delegate full privileges to another key. In fact I would argue that key should never leave the device, so for every device user should delegate a token from the one that is already has one. Alternatively new device could ask a token from service directly, granted it will have to prove it is under control of the same user. This can happen through a login flow from that device or possibly through emailed link or whatever.

I'm not sure I agree with statement that it makes (b) more likely. My understanding of consensus among security experts is that key reuse create wider attach surface and is less secure than other way round.

Either way even if one of the keys gets compromised, revocation should be possible and we should probably design it sooner than later.

- a. If the user loses their private key, they lose access to their bucket; we can’t implement any form of recovery.
- b. If the user’s private key is leaked, then they have no way of revoking it, so access to their bucket is permanently breached.
- c. The user could potentially create “backup” keys by creating UCANs with unlimited expiry times which delegate full privileges to another key. This helps mitigate problem (a), but makes problem (b) more likely.
- d. With no possibility of revocation or recovery, it conceptually breaks the idea that, like passwords, key-pairs can be rotated.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds upon misconception that user is tied to specific did which maps to specific bucket. I think this is bad way to conceptualize user <-> did relationship as 1:1. It is 1:n. It's much better to think of relation as user device <-> did.

And while indeed 1st did ends up been a bucket name it has no greater implication than just been a convenient way
to uniquely identify bucket. We could just as well generate another unrelated keypair in the backend, throw away private key and give new user access to a bucket corresponding to a key we threw away as opposed to did user provided.

That is to say there is no real significance in the fact that bucket ID maps to users first DID it's just convenient that is all. If this is too confusing we could just use CID of the user did instead and not tell anyone about it.

Furthermore with #1 we're basically creating DID for each document which is it's own bucket so users will have many buckets they will just interlink them as it makes sense.


## Scenario description

In this scenario we want to have multiple projects, ie. `myApp1`, `myApp2` projects. With the current architecture we could achive this by having `UserA` create a `didX/myApp1` and build a UCAN like `{with: 'storage://didX/myApp1', can: "*" }`. While this works it still keeps a strong relationship between those buckets and the original user.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App should just get it's own bucket.

If we think about it, assume `userA` has created a UCAN than grants `can: "*"` to `userB` for their entire bucket. Pratically, `userB` has the same level of auth as the original user, but from our DB perspective we're treating them "differently". There's no relationship between that userB and that bucket. At the moment the DID is column on the `User` table, so we are not linking to it.


I wonder if we should be thinking about Buckets as their own entities, that are accountable for signing UCANs (printing the tickets), and that ability can be delegated to multiple `did`s... But there's no hard 1:1 link between a single User and a single bucket. This would then leave open the possibility for things such as:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly this!



#### Questions
1. Who creates the keypair for the bucket in the first place? The user generating the bucket in the first place? The service?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User does


#### Questions
1. Who creates the keypair for the bucket in the first place? The user generating the bucket in the first place? The service?
2. I feel a relationship between the bucket creator should exist, but I reckon it should be an external key to user table.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is incorrect. User generates bucket with a new keypair, if they want to give full access to it to the user they just grant it because they have keys to do so.

#### Questions
1. Who creates the keypair for the bucket in the first place? The user generating the bucket in the first place? The service?
2. I feel a relationship between the bucket creator should exist, but I reckon it should be an external key to user table.
3. While UCAN would work for auth, I suspect the service (ie to hadle the UI), should still store information about delegation etc? Or am I missing something?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think right approach is following

  1. Bucket is created from keypair.
  2. Bucket creator right away grants access to the user that created it (in general case it's the same keypair).

If bucket keypair is lost or compromised, user can recover by sharing access (it was granted) through (2). If user keypair is compromised , user can recover by proving to service they are who they claim to be and regaining full access just with different keys.

I also think we should track path -> [did, expiryTime, startTime] relations as opposed to bucket <-> user relations, because access to be shared across buckets to specific DIDs and it does not matter who created the bucket in first place. (I thinking of this as one giant bucket of serviceDID is more accurate, and given service controls keypair it can share access to paths under it to arbitrary dids as it's pleased)

Through this viewpoint user can only change underlying keypair just like user can change password. And service can reissue all the UCANs that were granted to the fromer did to a new one simply by going over them and creating new ones to a new aud.

I hope this makes more sense.

@Gozala
Copy link
Collaborator

Gozala commented Sep 7, 2022

This seems pretty out of date from our current thinking so, I'm going to close it. Feel free to reopen / revive it.

@Gozala Gozala closed this Sep 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants