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

use did:web for the services so we can rotate keys without having to redo delegations #182

Closed
Gozala opened this issue Nov 17, 2022 · 16 comments
Assignees
Milestone

Comments

@Gozala
Copy link
Contributor

Gozala commented Nov 17, 2022

Currently we use did:key as service identifier. That is problematic because every time we want to rotate key we have to update the clients with a new key and old clients will no longer work.

It would make sense to just switch to did:dns so we can rotate keys when needed without having to upgrade all the clients

Out of scope

  • Actual DNS resolution is out of scope for now, on backend we should just have in memory mapping of did:dns -> dns:key for our keys.
  • Supporting other actors did:dns identifiers in delegation chains is out of scope (we can just error saying we failed to resolve the key).
  • Support keys that were rotated. So when we rotate the key we may get the the delegation chain which has our did:dns in the chain but with key that is no longer in rotation. We do need to support that use case long term, but for now it's out of the scope, we can work on this when we actually go about rotating keys
@Gozala
Copy link
Contributor Author

Gozala commented Nov 17, 2022

Here is some idea based on discussion https://filecoinproject.slack.com/archives/C02BZPRS9HP/p1668716338471879 an d https://filecoinproject.slack.com/archives/C02BZPRS9HP/p1668722431402189?thread_ts=1668716024.894469&cid=C02BZPRS9HP

  1. We create new "certification key" and keep it's private key very very secret (ideally in some hardware)
  2. We publish public "certification key" under did:dns:web3.storage.
  3. We have another "service key" that we rotate on regular bases (e.g. once a month).
  4. We publish public "service key" under did:dns:api.web3.storage.
  5. We also publish "service key certificate" (not sure where) which is public "service key" with "certification key".
  6. Ucans signed with did:dns:api.web3.storage will include fct like:
    { 'did:dns:api.web3.storage': { key: 'did:key:zCurrentServiceKey', certificate } }
  7. Our backend will have in-memory access to:
    a. Certificate public key
    b. Service key pair

Above setup would allow backend to do following:

  1. Resolve did:dns:api.web3.storage to did:key:zCurrentServiceKey without any IO.
  2. Verify that at a time when UCAN was issued did:key:zCurrentServiceKey was public key in rotation by
    a. By checking that certificate is did:key:zCurrent signed by certificate key.
  3. Profit

In other words I think we have a tractable solution to key rotation, we could harden this further by adding another layer, meaning we could have "supreme key" used for rotating "certificate keys" which is used to rotate "service keys".

@Gozala
Copy link
Contributor Author

Gozala commented Nov 17, 2022

I'm also observing some broad pattern here, where we could use UCANs inside UCANS as opposed to describing some custom fact encodings etc... What it boils down I trust some key principal, and it could state facts that I can trust without verification in other words, lets say we have this supreme did:key:zCertificateAuthority, then we could have UCAN like this:

{
   iss: 'did:key:zAlice',
   aud: 'did:dns:api.web3.storage',
   att: [{ can: "store/add", with: 'did:key:zAlice', root }],
   fct: [
    { iss: 'did:key:zCertificateAuthority',
      aud: 'ucan:',
      att: [{ can: 'did/set', with: 'did:dns:api.web3.storage', nb: { did: 'did:key:zServiceKey' } }]
    }
  ]
}

Basically we can generalize the whole idea of building up a lookup table using UCANs inside the facts, so if you can trust some did:key you can trust all of the facts it states

@blaine
Copy link

blaine commented Nov 17, 2022

Generally this makes sense (disclosure: I haven't seen the filecoin slack discussions).

I do think there are some questions about DNS proofs. Obviously if you have an X509 with a cn that matches the DNS signed by an entity in a reasonable cert chain, then you could just use that key, or/that is we could use Let's Encrypt's mechanism (ACME) here.

did:dns seems a bit comparatively complicated? The downside is that we'd need to figure out how to specify the verification of the X509 version if we go that route. did:web might be an alternative that's perhaps closer to ACME?

(aside: reading did:dns and did:web, these methods seem awfully "spec-first" and theoretical – are there examples of them being used in the wild? Not to be contrarian, just thinking about what gaining adoption would look like)

@Gozala
Copy link
Contributor Author

Gozala commented Nov 18, 2022

did:dns seems a bit comparatively complicated? The downside is that we'd need to figure out how to specify the verification of the X509 version if we go that route. did:web might be an alternative that's perhaps closer to ACME?

Why is dns:dns complicated it's just one TXT record that from which you can resolve did:key. I think did:web is more complicated because now you have to deal with DID documents with whole lot of things that goes into them.

@blaine
Copy link

blaine commented Nov 18, 2022

That's a good question! 🤔

Maybe it's not! I'm not sure, tbh. I have a gut feeling that there's a missing piece around key management wrt did:dns; how do I get that did:key, how do I store it, etc? Maybe all these things are simple and I'm just not used to the ecosystem!

@Gozala
Copy link
Contributor Author

Gozala commented Nov 18, 2022

I do think there are some questions about DNS proofs. Obviously if you have an X509 with a cn that matches the DNS signed by an entity in a reasonable cert chain, then you could just use that key, or/that is we could use Let's Encrypt's mechanism (ACME) here.

I think I need to do some reading on the subject to understand what you're describing.

(aside: reading did:dns and did:web, these methods seem awfully "spec-first" and theoretical – are there examples of them being used in the wild? Not to be contrarian, just thinking about what gaining adoption would look like)

I'm not sure. I'm approaching it from very pragmatic standpoint, which is:

  1. We need our service to have did so things could delegate to it without tying delegation to the key in rotation.
  2. We need to be able to rotate service key on regular basis without having to redo all the delegations.

It seems that we could use did:dns effectively with very little effort, because all we need to do to rotate a key is publish DNS record

_key1._did.danubetech.com. IN URI 100 10 "did:key:z6MkjvBkt8ETnxXGBFPSGgYKb43q7oNHLX8BiYSPcXVG6gY6"

Now secondary goal is to be able to do UCAN verifications without having to do bunch of DNS lookups (or any IO really). We could achieve that by having a "certificate key" that we sign rotations with and embed actual keys in UCANs. That way our backend only needs certificate public key to verify everything without even doing any DNS lookups

@Gozala
Copy link
Contributor Author

Gozala commented Nov 18, 2022

how do I get that did:key, how do I store it, etc? Maybe all these things are simple and I'm just not used to the ecosystem!

I think expected behavior is during verification you do a DNS lookup of for record that follows did:dns: and you get back did:key. I don't think you need to store it anywhere, although some caching is probably a good idea

But as I alluded to earlier we'd like to avoid even that DNS lookup which I think we could do by embedding something along the lines of dns-sd announcements in the UCAN itself (signed by trusted did:key, which in our service case will be hard coded but also published as DNS record so others could do the lookup)

@blaine
Copy link

blaine commented Nov 18, 2022

Nice. Yeah, I think in general I'd say if this works for your use-case, especially in a closed-loop environment, full steam ahead! I'm prematurely expanding out the thought process and applying this to the NNS context, where it would be great to be able to do this in a more general way and where e.g., using the existing keys people use for HTTPS might be a viable option to delegate DNS record -> did

@expede
Copy link

expede commented Nov 18, 2022

In the original flow described in the message starting

Here is some idea based on discussion https://filecoinproject.slack.com/archives/C02BZPRS9HP/p1668716338471879 an d https://filecoinproject.slack.com/archives/C02BZPRS9HP/p1668722431402189?thread_ts=1668716024.894469&cid=C02BZPRS9HP

It looks like the ID is rooted in a certificate authority.

  1. Is that a correct read?
  2. If so, what does this give you that a UCAN delegation wouldn't?

@expede
Copy link

expede commented Nov 18, 2022

(To clarify: for a lot — but not all! — cases, you can use a root UCAN as a de facto certificate authority. This keeps it in the authZ world though, and you may be trying to solve for authN?)

@Gozala
Copy link
Contributor Author

Gozala commented Nov 18, 2022

It looks like the ID is rooted in a certificate authority.

  • Is that a correct read?

No what I was calling a "certification key" is not related to CA and I realize maybe it was poor naming choice given the confusion it invoked.

It is simply a ED25519 key that we want to keep secure & use rarely, only when we rotate actual keys of our services.

  • If so, what does this give you that a UCAN delegation wouldn't?

I think our use case maybe be better explained by drawing a following delegation chain:

  1. Alice delegated to our service key
  2. Alice logged with other device so service delegated capabilities back
  3. Alice on new device invokes some capability with a service
did:key:zAlice -> did:dns:api.web3.storage -> did:key:zNewAlice -> did:dns:ap.web3.storage

If key we have rotated key and did:dns:api.web3.storage DNS record points to different key in step 3 we run into the problem: did:dns:ap.web3.storage resolves to did:key:zapiNew but signature in 2nd delegation was created with did:key:zapiOld. We could try and keep a log of key rotations so we could validate that invocation, but that introduces a lot of complexity and IO we're trying to avoid.

Proposed solution is as follows:

  1. When Alice creates delegation did:key:zAlice -> did:dns:api.web3.storage she also adds a fact into UCAN that provides did:dns:api.web3.storage = did:key:zapiOld mapping signed with that "certificate key". Idea is that this mapping is publicly available so can be embedded and verified.
  2. When invocation is validated we no longer need to do DNS lookup, instead we can look at the facts and
    1. Verify that fact can be trusted, because it is signed with that "certificate key"
    2. did:dns:api.web3.storage could be resolved to a key that was in rotation then, which is did:key:zapiOld
    3. Verify rest of the chain

In other words we can rotate service keys on regular basis (e.g. once a month) and that is also only time we need to access private key of "certificate key". Due to less frequent use we can keep it more secure and avoid storing it on our servers.

Can UCAN delegation be used here somehow ? Probably, which is kind of what this sketch does #182 (comment), is that a best option I don't know seems like it might be bit overkill.

@Gozala
Copy link
Contributor Author

Gozala commented Nov 19, 2022

I also started thinking that perhaps did:web would be a better option for us than did:dns because:

  1. resolution might be easier for web clients.
    • we do not currently need to resolve anywhere but backend, yet who knows how ppl might start using UCANs we issue
  2. Rotating batch of keys with DNS seems more painful than PR that updates DID document & all the code along with it

On the flip side did:dns is simple because it’s just domain → did:key mapping without any of the complex DID document structures.

Would love to hear feedback from @gobengo on all this

@Gozala
Copy link
Contributor Author

Gozala commented Nov 20, 2022

what does this give you that a UCAN delegation wouldn't?

Oh @expede it finally clicked 🤯 and now I no longer know how it was not obvious!

@Gozala
Copy link
Contributor Author

Gozala commented Nov 20, 2022

Ok so here is the deal and what I think @expede meant all along. We do not need to rotate web3.storage keys nor we need to use it for that matter. That key can delegate all it's capabilities to some other semi-permanent supervisor key and we could stash web3.storage key in a secure vault and hopefully never use it again. From that point on supervisor key is used to delegate all the capabilities (it was delegated) to the service key that is in rotation.

did:dns:web3.storage=did:key:zWeb3storage -> did:key:zSupervisor -> did:key:zService

With this setup:

  1. We can rotate service keys on regular bases.
  2. We can use supervisor key only to rotate service keys and therefor keep it under higher security.
  3. We use web3.storage key only to appoint a supervisor key, which is only the first time and the times supervisor key is compromised.

Now lets step through this delegation scenario

did:key:zAlice -> did:dns:web3.storage -> did:key:zNewAlice -> did:dns:web3.storage

did:key:zAlice -> did:dns:web3.storage

Here web3.storage service operating under did:key:zServiceBeforeRotation DID receives delegation from did:key:zAlice and has delegation from the web3.storage:

[
  // delegation from alice for an account recovery
  {
    iss: "did:key:zAlice",
    aud: "did:dns:web3.storage",
    att: [{ with: "did:key:zAlice", can: "*" }],
    exp: null,
  },
  // delegation chain from the web3.storage
  {
    // supervisor appoints key in rotation
    iss: "did:key:zSupervisor",
    aud: "did:key:zServiceBeforeRotation",
    exp: november, // <- is in service for a month
    att: [{ with: "*", can: "*" }], // <- all current and future capabilities delegated to me
    prf: [
      // web3.storage appoints supervisor key
      {
        iss: "did:dns:web3.storage", // <- DID lookup resolves to "did:key:zW3" kept in secure vault
        aud: "did:key:zSupervisor",
        exp: null, <- is in service until revoked
        att: [{ with: "*", can: "*" }],
      },
    ],
  },
]

did:dns:web3.storage -> did:key:zNewAlice

  • Here after was Alice has verified identity, and has requested access to her did:key:zAlice on did:key:zNewAlice.
  • It is November, so web3.storage key got rotated to zServiceAfterRotation.

Service can issue following delegation to did:key:zNewAlice

{
  // current key in rotation delegates to new alices device
  iss: "did:key:zServiceAfterRotation",
  aud: "did:key:zNewAlice",
  // all the capabilities I got from "did:Key:zAlice"
  att: [{ with: "did:key:zAlice", can: "*" }],
  prf: [
    // delegation from alice
    {
      iss: "did:key:zAlice",
      aud: "did:dns:web3.storage",    // <- wait but that resolves to did:key:zW3 not did:key:zServiceAfterRotation
      exp: null,
      att: [{ with: "did:key:zAlice", can: "*" }],
    },
    // well zServiceAfterRotation was appointed by did:key:zSupervisor
    {
      iss: "did:key:zSupervisor",
      aud: "did:key:zServiceAfterRotation",
      exp: december,
      att: [{ with: "*", can: "*" }],
      prf: [
        // which was appointed by web3.storage
        {
          iss: "did:dns:web3.storage",   // <- ok this still resolves to did:key:zW3
          aud: "did:key:zSupervison",
          exp: null,
          att: [{ with: "*", can: "*" }]
        },
      ],
    },
  ],
}

However ☝️ is not a valid delegation, or is it ? Logically it makes sense did:key:zAlice delegates to did:dns:web3.storage and did:dns:web3.storage delegates all capabilities to did:key:zServiceAfterRotation so it should be able to redelegate capability from did:key:zAlice, should not it ?

  1. I don't think so 🫣, because we don't really have a chain there.
  2. I don't think we really have with: "*" or a way to say whatever resources I may hold.
  3. Well our implementation certainly would not conclude that, it will say that claimed capability was delegated to did:dns:web3.storage=did:key:zW3 and not did:key:zServiceAfterRotation.

Assuming we can resolve the above issues some way, we'll be fully served by UCANs and no other clever schemes will be necessary to deal with key rotations.

@Gozala
Copy link
Contributor Author

Gozala commented Nov 20, 2022

I have put together a spec proposal for this stuff https://github.com/web3-storage/specs/pull/7/files

@gobengo gobengo self-assigned this Nov 30, 2022
@Gozala Gozala changed the title use did:dns for the services so we can rotate keys without having to redo delegations use did:web for the services so we can rotate keys without having to redo delegations Dec 8, 2022
@heyjay44 heyjay44 modified the milestones: w3up phase 1, w3up phase 2 Dec 15, 2022
@gobengo
Copy link
Contributor

gobengo commented Jan 4, 2023

fwiw there is a did:web now https://dev.uniresolver.io/#did:web:web3.storage

@gobengo gobengo mentioned this issue Jan 4, 2023
3 tasks
@Gozala Gozala closed this as completed Jan 5, 2023
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

No branches or pull requests

5 participants