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

What should be in the spec to support a Resolver Client verifying signatures using "old" keys #153

Open
swcurran opened this issue Dec 6, 2024 · 16 comments

Comments

@swcurran
Copy link
Collaborator

swcurran commented Dec 6, 2024

A major benefit of this DID Method is that a signature from a not-current version of the DIDDoc can be found. What features do we want to support in the spec to support a client to a resolver in doing that. In thinking about this, what guidance and aid is there in the DID Core and DID Resolution specs that can help us?

Things the resolver will and could do:

  1. A client can resolve a DID URL with a versionId and versionTime query parameter. A did:webvh resolver MUST (right?) return the corresponding version of the DID. The versionId can be either just the version number (1, 2, 3, ...) or it can be the full versionId with the entryHash. The versionTime does not have to be exact (does it?). It would be the DID version active based on versionTime in the log entries.
  2. A client can request a fragment that references something within a DIDDoc -- a key, service, etc. A did:webvh resolver MUST (or not?) return the most recent requested fragment -- even if it is from a DID version that is not current.
  3. Can we provide a way to give the client more information so that they can efficiently find a key that verifies a signature.
    1. A way to request the DID Log?
    2. A way to request an array of all DID Docs?
    3. A way to request an array of keys from all DID Docs?
  4. Since the "DID-to-HTTPS" transformation is trivial, a Client could resolve a DID (verifies the DID) and then retrieve the did.jsonl) file and extract out all of the DID Docs and keys.

It seems to me:

  • 1 is a no brainer (right??)
  • 2 is interesting and creates a very useful pattern -- the DID Controller uses the DID URL (with the fragment) as the key reference for the signature. I like this as being a valid option.
  • 3 would be nice to have, but even better would be DID Resolution support for that.
  • 4 is what a client that needed this support would do if 3 is not supported by a resolver.

Note: It would be great if the DID Core had a way to indicate the status of a key. The presence of a key in anything other than the current DIDDoc implies that nothing will be signed by that key. It would be nice if the DID Controller could indicate if any key ** SHOULD NOT** be used for verification. But I don't think we should do that unilaterally in the did:webvh spec.

@swcurran
Copy link
Collaborator Author

swcurran commented Dec 6, 2024

@peacekeeper -- would love to get your thoughts on this. Particularly item 2 in the description, and if there is something in the metadata that says "Hey, I resolved this thing, but it is not part of the current DIDDoc". Does that exist?

@peacekeeper
Copy link
Member

Great questions, here are some thoughts..

Re 1. The DID specs don't say anything about the structure of versionId. It's completely up to the DID method to define whether that is a sequential number, or a UUID, or a hash, or something else. Interestingly, this is the first time I hear about the idea that potentially multiple versionId patterns could be supported at the same time, i.e. a sequential number or a hash would BOTH be possible and identify the same version.

Somehow I have a feeling that this is not a good idea, because then I think there would be no way of telling if two different DID URLs identify the same version of the DID document or a different version. In addition, it's not clear to me how certain metadata properties in the resolution result would work in this case, such as the versionId metadata property (not parameter), and the nextVersionId. But we can discuss this on the next call.

Regarding versionTime, yes I agree with your description, i.e. the value doesn't have to be the exact timestamp of a version.

Re 2. Not sure what you mean by "the most recent requested fragment". The fragment will return a part of the DID document that is the result of dereferencing the DID URL without fragment. Examples:
- did:ex:test?versionId=1#key-1
- did:ex:test?versionId=1#key-2
- did:ex:test?versionId=2#key-1

The result of the first example may be different from the result in the last example, if #key-1 has changed between version 1 and version 2 of the DID document. And if version 1 of the DID document had a #key-1, but version 2 of the DID document does not have a #key-1, then the last example will return a notFound error. In other words, the fragment can only return something from the version of the DID document that was requested.

Due to the way how fragments work in URIs in general, there is nothing we can do in either the DID specs or DID methods specs to change this behavior.

Re 3. There are services out there that can do all this (I can show you), but I think it's out of scope of the DID Resolution spec, which should only cover resolving/dereferencing of single DIDs and DID URLs. I guess such advanced queries could potentially be defined in a separate spec.

Re 4. I agree, seems trivial. I would however question if there are really many scenarios where you would want to enumerate all DID documents or keys. E.g. for verification of a VC, shouldn't the proof point specifically to the DID URL of the verification method that must be used for verification? And this DID URL could contain both a versionId (or versionTime) and a fragment. In this case, you'd have a persistent, unambiguous pointer directly to the verification method, and I think there should be no need for enumerating over DID documents and keys?

@peacekeeper
Copy link
Member

It would be great if the DID Core had a way to indicate the status of a key

Verification methods in the Controller Documents spec have an expires and a revoked property, so I think it's likely that DID Core will support this too.

@swcurran
Copy link
Collaborator Author

swcurran commented Dec 9, 2024

Re 1: versionId. Does it help that the requirements of versionId is that the the version number component MUST increment on every version and the last part MUST be the entryHash with a formal spec for the calculation? I don’t think there is any way for your concern (“if two different DID URLs identify the same version of the DID document or a different version”) to happen — the versionID and its component parts always identify a unique version of the DID.

Re 2: The question was slightly different, but your response needs clarification.

From your example, I was thinking more about if the DID URL being resolved was just <did>#key-1 — only the fragment, no versionId query parameter — and there are multiple DIDDocs with key-1 (as in your example). In your case, it would resolve to the one is versionId=2 — the most recent version. This always work if the fragment is the same in all versions, but what if it is different in different versions?

I guess would could put in a requirement that all fragment identifiers MUST have identical content if they appear in multiple versions of the DIDDoc, but that is extra verification work.

Re 1 Part 2: The bigger question I was asking was if the DID URL was <did>#key-1 and key-1 was in an older versioner of the DIDDoc, but not in the current, is there guidance from the DID Resolution spec on what to do. Notably:

  1. Should the fragment be returned or not in that case?
  2. If the fragment is returned, is there anything in the metadata to say “Hey, here’s the fragment, but it is not from the most recent version of the DIDDoc”.

Thanks!

@peacekeeper
Copy link
Member

Re 1: Hmm I don't understand. First you said "The versionId can be either just the version number (1, 2, 3, ...) or it can be the full versionId with the entryHash". That sounded like some ambiguity that multiple different versionId values are supported for a given version. But if there is only a single unique versionId value for each version, then everything is fine.

Re 2: I don't really understand what you are trying to accomplish or what's the problem... To me it's a feature that the result of <did>#key-1 can change over time, by updating the DID document.

if the DID URL was #key-1 and key-1 was in an older versioner of the DIDDoc, but not in the current

In this case, dereferencing <did>#key-1 would return a notFound error, since #key-1 doesn't exist in the current DID document. There is nothing we can do or change about this in the DID Resolution or DID Method specs, it's just how URIs always work.

@swcurran
Copy link
Collaborator Author

Re 1: Sorry I wasn’t clear. The DID Controller MUST create the versionId as “<n>-<entryHash>”, where the <n> is the number of the version which MUST increment from 1, and <entryHash> MUST be calculated (as defined in the spec) across the entry. A resolver MAY use ?versionId=<n> or ?versionId=<n>-<entryHash> (aka ?versionId=<versionId>), both of which return the relevant version of the DIDDoc. Given that clarification, what do you think?

Re 2: It’s unfortunate that the result is a notFound in the example I gave, and I think we should consider changing that in the future. The use case is a big one, I think. As an issuer I sign credentials, from time to time updating my signing key. If I use the same #key1 all the time, then the older credentials will fail validation, unless the verifier goes through the process of finding the right instance of #key1 — a laborious task of trial and error or trying to use versionTime. If all the different keys in the DIDDoc versions have a different identifier, I think it would be good if the resolver could find the right key via that fragment. Evidently, that is not possible.

The obvious answer is that when signing a credential, the Issuer in that situation (all issuers?) would use reference the key via the DID URL: <did>?versionId=<versionId>#key1 to have the resolver “automatically” resolve that reference to the key used. More verbose, but viable.

Either way, it would also be nice to be able to say in a later DIDDoc version that #key1 is revoked (vs. simply obsolete). That is an expensive operation for most DID methods (it would be cheapest in did:webvh, just because…), but would be valuable metadata. revoked in this case meaning don’t accept a signature with this key vs. the “normal” — I was using that key, and its fine, but I’ve moved on to another key.

@peacekeeper
Copy link
Member

Re 1: See my earlier comment #153 (comment), somehow I feel it's better to have only a single valid versionId per DID document version. But not sure, maybe it's fine to allow different patterns; in the current DID specs it's not forbidden.

Re 2: What you are asking for is like saying that when you open https://example.com/index.html#overview, your browser should show the #overview section of the index.html page, even if that section has been removed at some point and doesn't exist anymore in the current version of the page.

One solution is to reference the version explicitly, as you said e.g. <did>?versionId=<versionId>#key1

Another solution is to keep all keys in the DID document and mark them as revoked instead of removing them, this will be supported via controller documents, see my earlier comment #153 (comment).

@swcurran
Copy link
Collaborator Author

Good example: What you are asking for is like saying that when you open https://example.com/index.html#overview, your browser should show the #overview section of the index.html page, even if that section has been removed at some point and doesn't exist anymore in the current version of the page. — But that is the point of having a verifiable history — and especially given the use case for signature, verification and keys. But I get it.

One hopes that the revoked use case is rare — e.g., when a key is found to be compromised and being used maliciously vs. imply being rotated away. Since it (likely) rare, the overhead of keeping all the keys around and having longer and longer current DIDDocs (especially when you have a verifiable history) is not appealing. Possible, but doesn’t feel good. I’ll keep thinking about it. Perhaps we can put something in specific to did:webvh by using parameters to convey revocation.

@brianorwhatever
Copy link
Contributor

Re 1:

What Stephen has said here isn't quite what I have been picturing / implementing in the typescript implementation.

A resolver MAY use ?versionId= or ?versionId=-

I have implemented it thinking we would be doing ?versionNumber=<n> or ?versionId=<n>-<entryHash>. I think the spec is clear that versionId is the version number + entry hash joined by a "-"

Re 2:

I think what the reality of how hash fragments works means to me is that we have to suggest in the spec that if you are looking for a verification method (to verify a proof) you will likely need to query the versionTime that the proof was created and look in the resolved document for that time to grab the key with that fragment.

@swcurran
Copy link
Collaborator Author

Re 1: Interesting. There’s no such thing as versionNumber in either the DID Core spec, or currently in the did:webvh spec. Do we need to add it? And if we are going to “ignore” the DID Core spec for this, then why not just use versionId, but use our “hacked” definition of allowing just the versionNumber as valid?

Re 2: I think the versionTime approach will be used if the <did>#<fragment> is all you have, it is kind of ugly — not deterministic. If it becomes the practice of issuers to use <did>?versionId=<versionId>#<fragment> as the key reference (which I think we should encourage), then it becomes deterministic what key they are going to get.

@brianorwhatever
Copy link
Contributor

then why not just use versionId, but use our “hacked” definition of allowing just the versionNumber as valid?

for exactly that reason.. It is a "hack" as you've said. The versionId is a very specific thing. Calling it another thing as well doesn't make sense to me. Querying by version number to me isn't all that important to me to be honest. Would be a nice to have definitely but not a deal breaker if it doesn't make sense. That being said we should be able to call it out in this spec and then leave it at that? I don't know exactly how difficult it is to add another resolution type to DID Core or DID Resolutions or wherever it should be..

not deterministic

Why is that? The versionTime in the log won't change so a resolver will always return the same document given a time query

@andrewwhitehead
Copy link
Contributor

andrewwhitehead commented Dec 13, 2024

This thread is spinning a little but we haven't really talked about what happens when you actually revoke (or just rotate) a key.

It's not necessary to remove the entity referenced by the verificationMethod from the DID document in order to revoke it, even ignoring the revoked property from the controllers spec. It simply has to be removed from its verification relationships (authentication, assertionMethod, etc). So the key definition could be left in the document with some metadata about how and when it was revoked, giving the verifier a way to decide on the next step. It could even be moved into a separate section (revokedKeys?) if you wanted to avoid confusion by lazy verifiers.

Importantly, removing all verification relationships means that associated proofs MUST NOT verify as-is, although it is up to the verifier if they want to take extra steps. I believe this step should only be taken for compromised keys, or keys for which all associated proofs are considered expired. If the controller wants to allow verification for historical proofs, then don't remove the key definition or the verification relationships. I imagined something like a validUntil property for rotated keys which aren't currently being used for signing, but are still valid for proofs created before a certain time. That seems a little different from the expired property defined by the controllers spec.

Referencing a specific versionId in the verificationMethod of a proof means that you cannot effectively revoke the key, as verifiers will not see later updates to the DID document. Unless we implement an external key revocation method as mentioned in the DID core spec:

Even if a verification method is present in a DID document, additional information, such as a public key revocation certificate, or an external allow or deny list, could be used to determine whether a verification method has been revoked.

@swcurran
Copy link
Collaborator Author

@brianorwhatever and this comment. Good points — thanks.

I think being able to resolve via versionNumber is still valid (or we could leave it off). If we are going to use that, we need to add it to the did:webvh spec, agreed? We can then debate adding it to DID extensions. I’ll put it into a PR (the one I have going?), and allow all to comment. I’d like it in v0.5.

Re: versionTime: I guess it is — reasonable. It doesn’t feel great, but you are right, in the majority of use cases we should expect, it is sufficient. I’ll scan through to see if this should be in the spec or just in the implementation guide.

@swcurran
Copy link
Collaborator Author

@andrewwhitehead and this comment. Let me understand what you are proposing:

  • a rotated key is left in later versions of the DIDDoc, with all references as to its uses.
  • a revoked key is also left in the DIDDoc, but its uses are removed and metadata added to say “revoked” and perhaps even a reason/impact.

Is that right?

Assuming so — I really hate the idea that we have both the full history of the DID AND we keep an extra copy of that data in an ever growing current DIDDoc. If did:webvh uses the DNS certs model of rotations every 90 days, we’re going to have both a very long DID Log file, and a very long latest version of the DIDDoc. I don’t mind one or the other, but I hate both.

Since I’m a fan of the DID Controller making the key reference the active key at the time of signing (e.g. <did>?versionId=<versionId>#<key-fragment>), whatever you put in a later DIDDoc about the key is not going to be helpful. I do see two options:

  • An external file of “revoked” keys
  • A revoked-keys that is maintained in all new versions of the DIDDoc (so a resolver can always find it in the latest) that has the list of revoked keys — presumably just <did>#<key-fragment>.

Either way, in using an old key, the resolver would have to check one more place — either the external file or the latest DIDDoc.

@andrewwhitehead
Copy link
Contributor

@swcurran It's also possible to remove a revoked key entirely, in fact the DID core spec says that's the only unambiguous way to do it. I was just suggesting another option for discoverability, but for short term keys that are single-purpose and frequently rotated I don't think there's any need to maintain them all in the current document.

Since I’m a fan of the DID Controller making the key reference the active key at the time of signing (e.g. ?versionId=#), whatever you put in a later DIDDoc about the key is not going to be helpful.

If this method means inventing an external revocation mechanism then maybe it's not worth it? An issuer is always free to do it that way, but I don't like the trade-off. It's not at all clear how and when the external revocation would be applied for a verifier that is considering this just another DID method (perhaps using the universal resolver) and parsing the document for keys. Is the resolver meant to rewrite the old document to remove them?

@swcurran
Copy link
Collaborator Author

@brianorwhatever -- see the commit I just added to the PR. Need to add it to the log as well, but what do you think?

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

4 participants