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

How to authenticate a DID rotation #3

Closed
brianorwhatever opened this issue Mar 7, 2024 · 28 comments
Closed

How to authenticate a DID rotation #3

brianorwhatever opened this issue Mar 7, 2024 · 28 comments
Labels
discuss Let's talk about this at our next meeting

Comments

@brianorwhatever
Copy link
Contributor

The method must only allow rotations of DID documents by the controller.

Controller is a bit of a loaded term but my assumption is that this is the controller property of the DID doc. If so, the first document will be lacking this property won't it? What do we expect a controller property to look like? It can be a set of keys but we would have to further specify (see #2)

@andrewwhitehead
Copy link
Contributor

The DID spec is light on the details of how controllers should be interpreted, leaving the explanation to specific DID methods it seems. A good number of the open issues have to do with the semantics of controllers: https://github.com/w3c/did-core/issues?q=is%3Aissue+is%3Aopen+controller

We can say that the top-level controller field is optional, and can be a DID string or set of DID strings identifying other subjects. If provided, then only the set of controllers are allowed to make changes to the DID document (and presumably, should authorize version 0 as well). If left out then we would want the DID subject to be authorizing changes on their own behalf, as opposed to them not requiring any authorization.

Looking at did:cheqd as an example, it seems reasonable to require signatures from ALL controllers on an update, from keys present in the authentication section of their respective DID documents (although we can consider capabilityInvocation instead).

@swcurran
Copy link
Collaborator

swcurran commented Mar 7, 2024

After thinking through this and texting with Drummond and @peacekeeper, I think this is the best we can do if we want to enforce that only an authorized key (or keys) can update the DIDDoc of a DID. The first part of this covers the single key use case of a verificationMethod, and lower down in the comment I propose adding in multi-sig via verifiableCondition.

  • We want to have a proof on a DIDDoc from a key (or keys) authorized to update the DIDDoc.
  • Per the DID spec, the top level controller item (string or set) contains the DID(s) of the party(ies) authorized to update the DIDDoc — e.g. to add a new version to the DIDDoc
    • However, by the spec, the controller must be a DID without a fragment, meaning it can’t reference a verificationMethod in the DIDDoc, so it is not a straightforward reference to a specific key within the DIDDoc.
  • Thus:
    • The top level controller is the DID or DIDs authorized to update that DIDDoc.
      • If the controller references one or more different DID(s), resolve those DID(s), else the DID is self-referencing.
      • If the controller item is missing, assume the DID is self-referencing.
    • Each verificationMethod in the controller DID(s) is authorized to sign/update this DID's DIDDoc.
      • An issue I see with that is a given verificationMethod might be present to be used for purposes other than to update the DIDDoc, such as to sign a VC. But so be it. By inclusion, it is authorized.
      • A way around that is to have DIDs that control updates to DIDs used for other purposes. So an entity might have one controller DID for updating a set issuer DIDs that can’t update themselves, but rather are only updated by the one controller DID.

For multi-sig, we define that a verifiableCondition is used equivalently to a verificationMethod for the purposes of updating the DIDDoc of a DID:

  • Use verifiableCondition2021 for multi-signature management.
  • A verifiableCondition can be used wherever a verificationMethod can be used.
  • Notably in this DIDMethod, as proof of authorization to update a DIDDoc.
    • That is, the DIDDoc of a controller DID contains a verifiableCondition entry.
  • To update a DIDDoc, a single signature from a controller DID verificationMethod (if any) is sufficient, or a set of signatures that meets a controller DID verificationCondition (if any) threshold.

@brianorwhatever
Copy link
Contributor Author

A thought I've had after reading the did:plc spec was that maybe the self referencing controller property CAN be a version or specific point in time reference of the DID. To do this we can have the method specify that the controller property isn't included in the document but injected after using a new SCID. So the id of a DID would remain as the inception SCID but the controller would be updated every rotation to identify the current state. If we also want to include a way to have other DIDs sign off on changes this would still be possible if we say that a controller property can be included but the current SCID value is added onto the list.

I also like that they have a ranking of recovery keys in their list but I don't like that they allow you to make changes with a higher authority for 72 hours so I'm not sure we can do anything with that idea..

did:plc also separates out the recovery keys from the DID Document keys entirely and I think that separation of concerns is interesting but probably unnecessary.

verifiableCondition seems like a good approach to multisig 👍

@andrewwhitehead
Copy link
Contributor

I agree that the keys that are allowed to update the DID document should be separate from the keys used for other purposes, like signing VCs. Based on my reading of the DID Core spec, capabilityInvocation is intended primarily for authenticating with external services (like a VDR) and so maybe not appropriate here. For VCs, the assertionMethod proof method is used. The default option, authentication does seem to make sense for authorizing DID rotations, as the text says:

A particular DID method could decide that authenticating as a DID controller is sufficient to, for example, update or delete the DID document.

It gets a little complicated for me when considering external controllers:

  • When the DID document is self-managed, including a proof with the proofPurpose set to authentication makes sense. Here is my self-signed proof, for the purpose of authenticating this update.
  • When your proof is provided by an external controller, it feels more analogous to asserting claims, ie. the purpose could be assertionMethod. It's not really 'authentication', is it?

@swcurran
Copy link
Collaborator

swcurran commented Mar 8, 2024

@andrewwhitehead and I talked about this and we think we have a generalized approach, with some “details” that we can adjust based on feedback. Basically:

  • controller might be self-referencing (default) or an external DID or DIDs.
  • For every controller DID, we find all of the key types didDocUpdate, which will presumably (although we don’t care) will reference either a verificationMethod or a verifiableCondition.
    • The key type didDocUpdate is a detail — we will use what the community recommends (e.g. invocationCapability, authentication, etc.). It seems logical that there be a specific key type that we would want to use for that — hence the name we are making up. If it is acceptable to the community, it is an extension for now, with the idea of it going into the DID spec.
    • What do we do if there are no didDocUpdate key types in the controller DIDs?
  • An acceptable proof to update a DIDDoc consists of a signature from a SINGLE didDocUpdate key.
    • Alternatively, this could be "a proof with a signature from ALL of the didDocUpdate keys”. This is a detail and we can adjust spec/implementation if we need to.
    • I think that single makes more sense because anyone creating a DID can always use a verifiableCondition to enforce the use of multiple keys and we should encourage that approach.

@andrewwhitehead
Copy link
Contributor

^ One caveat here is that an external controller probably wants to separate the keys they use to update their own DID Document versus the keys used to update (authorize an update to) someone else's DID Document.

@swcurran
Copy link
Collaborator

swcurran commented Mar 8, 2024

Arrgghh…turtles all the way down. Yes — I think I momentarily thought about that and decided to suppress the idea.

Ah…does the controller item on the key type come into play here? What is it for? Of course, if so, the chicken and egg SCID becomes even more fun.

@andrewwhitehead
Copy link
Contributor

In regards to the controller field on a verification method, the spec says:

Since a key can't control itself, and the key controller cannot be inferred from the DID document, it is necessary to explicitly express the identity of the controller of the key.

Essentially it is meant to point to the entity that controls the key data. Manu adds some explanation here: (w3c/did-core#802 (comment)). Looking at the different combinations of key IDs and controllers, with an example DID document did:example:child:

  1. The document could have a signing key with ID did:example:child#key-1 and the controller set to did:example:child, this is the common case. I don't think we need to expand on it.

  2. It could have a signing key with ID did:example:child#key-1 and the controller set to did:example:parent. In a signature using this key, the verification method would be set to did:example:child#key-1 which would be resolvable via the current document. As I understand the semantics, the verifier would need to then look up the DID document for the controller did:example:parent (based on the verification method it resolved) to see if it listed the same key for the same proof purpose. That document would need to use the same key ID. If additional properties were found alongside the reference, then presumably those would have to match the definition in did:example:child.

  3. It could have a signing key with ID did:example:parent#key-1 and the controller set to did:example:parent. Verifying the signature on a proof would mean looking up the DID document for did:example:parent, resolving the key, and seeing whether it is listed for this proof purpose. If the proof is being used to authenticate did:example:child (which depends on the context and interpretation the proof), then the verifier would have to check that the key ID is also listed in the did:example:child document for this proof purpose. The same matching rules would apply as above.

  4. The document could have a simple reference to an external key such as did:example:parent#key-1 with no other details, listed for the appropriate purpose (ie. authentication). In this case the original key definition must be resolved via did:example:parent to see who the controller is, and it must be listed for the same proof purpose. Now the controller on this external key definition could be another DID subject, meaning that a third DID resolution would need to be performed (!) and the key definitions again compared for equality.

@andrewwhitehead
Copy link
Contributor

I believe that given the RDF interpretation of controller it does make sense to only require one signature, not signatures from all controllers. For a document with two controller entries listed (such as did:example:A and did:example:B), we have two independent rules asserting that did:example:A controls did:example:child and did:example:B controls did:example:child. For other cases the two controllers can either create a new shared DID to control did:example:child (what the spec calls group control), or they can add verifiable conditions rules to the current document.

@swcurran
Copy link
Collaborator

In regards to the controller field on a verification method, the spec says:

Wow…that seems very unhelpful. The “same key” stuff seems very unhelpful.

Do you understand that well enough to know what we need to do? The issue seems to be that there is no key type (e.g. verificationMethod, authentication, etc) that is explicitly for controlling the DID — which is where we got to the other day. We need to pursue that with the “DID Folks” (Manu et. al.) on what they recommend, and/or we push for a key type that we use. Either way it is just a “rule” that we enforce, so not a big impact on the code — much more of a standards/policy/people issue.

@andrewwhitehead
Copy link
Contributor

I would suggest for self-signed documents, use the authentication proof purpose. The text is consistent with that being a viable option.

For external controllers, we can't predict the DID method that will be used, and so we can't assume they are able to add new proof purposes. For this reason, one of the existing purposes should be chosen. authentication could be used, but it seems like it may be good practice to reserve that for the 'self-signed' case. For this reason I would suggest using the assertionMethod proof purpose when an external controller is authorizing a document update. This is the same as when they sign a VC, but presumably with a different key. There's a closed issue from Daniel Hardman in regards to limiting the scope of assertion methods that could apply here (it could also apply if authentication was used) but that would likely require a new extension.

@swcurran
Copy link
Collaborator

Hmmm…ok I guess, but weird that we would use a differnt type depending on whether it is an internal vs. an external controller DID. Not my favourite and it does make the code more complicated.

@swcurran
Copy link
Collaborator

Planned approach:

  • get a list of DIDs from the top level controller item in the DIDDoc.
  • If omitted, use the id of the DID.
  • If a list, any of the DIDs listed are authorized to sign.
  • Since the controller item contains DIDs, the key reference rules are, for each DID:
    • Collect any authentication key, if found. If none, continue.
    • When we add support for it -- use any verifiableCondition keys/thresholds found. If none, continue.
    • Use any verificationMethod keys. If none found -- no authorization keys fro this DID.
  • Any keys found can be used. verifiableCondition keys must be used in combination to achieve the threshold signing.

For signing, the DID controller would indicate what key reference(s) are to be used, and provide an endpoint for a signing service. The DI proof would contain the references to the key(s) used.

For verifying, the verifier would confirm that the reference(s) to the keys are in the authorized list (with the threshold met if using verifiableCondition), retrieve the public keys via the references, and verify the signature(s).

@andrewwhitehead
Copy link
Contributor

I think in the interest of keeping validation efficient, we should only look at verification methods (authentication keys) in the current document and NOT require resolution of any controller DIDs. Every proof would have to reference a key within the previous version of the document.

The verification methods in the document may have an external controller value set, which would have to be listed in the DID doc controllers as well, but I suggest we should not take the step of resolving that controller and checking that the key is also present there, at least when used for the purpose of DID document updates. If the key is used for other purposes then of course verifiers are capable of doing that extra check.

@swcurran
Copy link
Collaborator

I think you are indicating a difference in what should be in the spec. vs. an implementation. I think the spec. should indicate that anything valid is…well, valid. So if the DID wants to put external DIDs into the controller property, it can do so.

For verification, the key reference being used in the proof is in the proof itself (right?). A verifier only needs to resolve an external DID if the key reference is to an external DID and that DID is listed in the DID controller property. Further, if it can’t resolve the external DID (e.g., unsupported DID Method) that is the result of the resolution.

For the controller signing an update, it is up to the DID Controller to decide what key reference is to be used for the DID. It can decide to only use internal keys, to use an external DID or even to list multiple DIDs and then use whichever one it needs.

@swcurran
Copy link
Collaborator

New proposal:

  • if an external DID/DID key reference is a did:tdw or did:web, a verifier will resolve it and use it.
  • Else -- we recommend that the DID controller put a copy of the verificationMethod into the DID itself.
  • Else -- resolution fails with an NOT RESOLVABLE error.

The reason I think the first is important is the typical Multi-Sig use case -- such as when a Board of Directors control the DID of an entity. In that case, it is likely all of the DIDs are of the same type, and so resolvable. It does create the possibility of turtles all the way down, but...

@andrewwhitehead -- acceptable?

@swcurran swcurran added the discuss Let's talk about this at our next meeting label Mar 27, 2024
@brianorwhatever
Copy link
Contributor Author

This creates an external dependency such that another DID is able to break the history of my DID. For example If an external did:web document were to disappear the log of my DID would be unresolvable. But that is likely a caveat of external controllers no matter what we do.. it scares me though

@andrewwhitehead
Copy link
Contributor

andrewwhitehead commented Mar 27, 2024

I feel like it should be up to the client to resolve an external controller for extra verification if that is what they want to do. The resolution process is just returning the current state of the DID document and checking the internal consistency of the history.

@swcurran
Copy link
Collaborator

This creates an external dependency such that another DID is able to break the history of my DID. For example If an external did:web document were to disappear the log of my DID would be unresolvable. But that is likely a caveat of external controllers no matter what we do.. it scares me though

I disagree. It’s not your DID, it’s “their” DID, since they are a controller. That’s the point of a controller.

I feel like it should be up to the client to resolve an external controller for extra verification if that is what they want to do. The resolution process is just returning the current state of the DID document and checking the internal consistency of the history.

The problem with this is that we’re saying — “We’re resolved the DID and verified the history. Well, most of it. Over to you.”. I don’t see a problem with resolving DIDs that we know we can (at least try) to resolve. Agree we should not include all DIDs, hence the limit to the DIDs we know.

But you do raise the interesting case of resolving the DID that we can’t full verify, and (somehow…) communicating to the caller that we did that. AFAIK — there is no way the client to a resolver can know that the resolution is only partially complete. They only get a DIDDoc from the resolution — they don’t get the log.

@brianorwhatever
Copy link
Contributor Author

I disagree. It’s not your DID, it’s “their” DID, since they are a controller. That’s the point of a controller.

yes fair, but I mostly mean it creates a technical external dependency. So the resolution of a DID that has a did:web external controller brings in all the baggage of that DID (ie DNS resolution, server availability, latency, key availability (no history for did:web). We just need to make it clear the tradeoffs this introduces

@swcurran
Copy link
Collaborator

Solution that we're going to put in the first draft:

We recommend that the DID controller put a copy of the verificationMethod into the DID itself.

@brianorwhatever
Copy link
Contributor Author

Interestingly, the digital bazaar libraries require a controller property on a Verification Method. Things blow up if it doesn't see that. This makes me think pulling in verification methods from other DIDs isn't all that strange of an idea. My original concern about "what if the external controller revokes there key externally but forgets to revoke it here" still scares me.. This external controller thing sure creates a lot of fun edge cases 😄

@brianorwhatever
Copy link
Contributor Author

Ok, I think I have removed my fear of that. The main controller can revoke the key themselves.. so they can be watching the other controllers did doc themselves and then revoke it themselves should there be any funny business

@brianorwhatever
Copy link
Contributor Author

To update a DID Document a key MUST be listed as a verificationMethod and included in the authentication key list. The controller of that key MUST be listed as a controller of the DID Document.

@brianorwhatever
Copy link
Contributor Author

brianorwhatever commented Mar 28, 2024

@swcurran
Copy link
Collaborator

Questions about the implementation:

  • Is it a requirement that the authentication key from the controller DID MUST be present, or if not present, a verificationMethod from the DID controller can be used?
    • e.g., does resolution fail if authentication is not present?
  • Is it right that if/when the verifiableConditions support is added, that it would be able to be used wherever verificationMethod can be used?

@brianorwhatever
Copy link
Contributor Author

Yeah right now I am requiring authentication type. This is actually something I realized is wrong about my mental model of DIDs. I used to think keys always had to be in one of the relationship lists and then keys were listed in verificationMethod so that they could be repeated with multiple relationships. But it sounds like most people treat the verificationMethod list as a catch all so keys in there are valid for any purpose. I'll have to update both my mental model and the implementation 😅

Unfortunately I haven't had a chance to investigate verifiableCondition enough so I can't answer that second question

@andrewwhitehead
Copy link
Contributor

I don't think that keys in verificationMethod should be acceptable for just any purpose, but it seems to be preferred to put the full key definition there and only place references in the other properties.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss Let's talk about this at our next meeting
Projects
None yet
Development

No branches or pull requests

3 participants