w3c / did-core

W3C Decentralized Identifier Specification v1.0
https://www.w3.org/TR/did-core/
Other
409 stars 95 forks source link

When should did parameters be dropped? #337

Open OR13 opened 4 years ago

OR13 commented 4 years ago

Consider:

did:example:123?q1=1&q2=2#fragment => did document.

should didDocument.id === did:example:123?q1=1&q2=2 or did:example:123 ?

this is particularly concerning for interactions with the initial-state parameter used by sidetree and didcomm... if its dropped the URI becomes unresolvable, so that client would need to store that somewhere...

tplooker commented 4 years ago

To add further detail to this issue, calling to an ion based sidetree node with the following request

curl http://localhost:3000/identifiers/did:ion:test:EiC0wVS3K9VLv-XZVL55i8Y4d1Hk1kJpeckfDXIu0nM17A?-ion-initial-state=eyJkZWx0YV9oYXNoIjoiRWlDUlRKZ2Q0U0V2YUZDLW9fNUZjQnZJUkRtWF94Z3RLX3gwV0gtZXVhNGhrZyIsInJlY292ZXJ5X2NvbW1pdG1lbnQiOiJFaURmR2k0NzRpajQ5VnFfeWlLRzFxOG9DMWpIX0JGTGZIMjdCSVFLVW5UQlR3In0.eyJ1cGRhdGVfY29tbWl0bWVudCI6eyIwIjoxOCwiMSI6MzIsIjIiOjIyMywiMyI6MjYsIjQiOjQ2LCI1Ijo1OSwiNiI6MjI2LCI3Ijo0MCwiOCI6MjQ4LCI5IjoyNDUsIjEwIjo5MCwiMTEiOjE5MSwiMTIiOjIwMiwiMTMiOjM0LCIxNCI6MTM0LCIxNSI6MjE0LCIxNiI6MTc1LCIxNyI6NDAsIjE4IjoxMSwiMTkiOjg4LCIyMCI6MTk5LCIyMSI6MjUyLCIyMiI6MTcsIjIzIjo3NSwiMjQiOjEyNCwiMjUiOjEyNSwiMjYiOjE4NywiMjciOjQsIjI4IjoxMzIsIjI5IjoxMCwiMzAiOjgyLCIzMSI6MTE2LCIzMiI6MTkzLCIzMyI6Nzl9LCJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljX2tleXMiOlt7ImlkIjoielEzc2hta2VNZmh1RHdOOGExS3YiLCJ0eXBlIjoiU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOCIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6InNlY3AyNTZrMSIsIngiOiJhYTZhNDVhM1lnRW1sYks2cjVwYmlCcDhkV2RVZTZ2bUJsWWhIS2dhWGRRIiwieSI6InZPR1A2cVVZMUt3cFk3VFpENzI5c2p3S2d0b0hPZ1NyY1pHMmFUSExQYWsifSwicHVycG9zZSI6WyJnZW5lcmFsIl19XX19XX0 -i

Results in

{
   "@context":"https://www.w3.org/ns/did-resolution/v1",
   "didDocument":{
      "id":"did:ion:test:EiC0wVS3K9VLv-XZVL55i8Y4d1Hk1kJpeckfDXIu0nM17A?-ion-initial-state=eyJkZWx0YV9oYXNoIjoiRWlDUlRKZ2Q0U0V2YUZDLW9fNUZjQnZJUkRtWF94Z3RLX3gwV0gtZXVhNGhrZyIsInJlY292ZXJ5X2NvbW1pdG1lbnQiOiJFaURmR2k0NzRpajQ5VnFfeWlLRzFxOG9DMWpIX0JGTGZIMjdCSVFLVW5UQlR3In0.eyJ1cGRhdGVfY29tbWl0bWVudCI6eyIwIjoxOCwiMSI6MzIsIjIiOjIyMywiMyI6MjYsIjQiOjQ2LCI1Ijo1OSwiNiI6MjI2LCI3Ijo0MCwiOCI6MjQ4LCI5IjoyNDUsIjEwIjo5MCwiMTEiOjE5MSwiMTIiOjIwMiwiMTMiOjM0LCIxNCI6MTM0LCIxNSI6MjE0LCIxNiI6MTc1LCIxNyI6NDAsIjE4IjoxMSwiMTkiOjg4LCIyMCI6MTk5LCIyMSI6MjUyLCIyMiI6MTcsIjIzIjo3NSwiMjQiOjEyNCwiMjUiOjEyNSwiMjYiOjE4NywiMjciOjQsIjI4IjoxMzIsIjI5IjoxMCwiMzAiOjgyLCIzMSI6MTE2LCIzMiI6MTkzLCIzMyI6Nzl9LCJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljX2tleXMiOlt7ImlkIjoielEzc2hta2VNZmh1RHdOOGExS3YiLCJ0eXBlIjoiU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOCIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6InNlY3AyNTZrMSIsIngiOiJhYTZhNDVhM1lnRW1sYks2cjVwYmlCcDhkV2RVZTZ2bUJsWWhIS2dhWGRRIiwieSI6InZPR1A2cVVZMUt3cFk3VFpENzI5c2p3S2d0b0hPZ1NyY1pHMmFUSExQYWsifSwicHVycG9zZSI6WyJnZW5lcmFsIl19XX19XX0",
      "@context":[
         "https://www.w3.org/ns/did/v1",
         {
            "@base":"did:ion:test:EiC0wVS3K9VLv-XZVL55i8Y4d1Hk1kJpeckfDXIu0nM17A?-ion-initial-state=eyJkZWx0YV9oYXNoIjoiRWlDUlRKZ2Q0U0V2YUZDLW9fNUZjQnZJUkRtWF94Z3RLX3gwV0gtZXVhNGhrZyIsInJlY292ZXJ5X2NvbW1pdG1lbnQiOiJFaURmR2k0NzRpajQ5VnFfeWlLRzFxOG9DMWpIX0JGTGZIMjdCSVFLVW5UQlR3In0.eyJ1cGRhdGVfY29tbWl0bWVudCI6eyIwIjoxOCwiMSI6MzIsIjIiOjIyMywiMyI6MjYsIjQiOjQ2LCI1Ijo1OSwiNiI6MjI2LCI3Ijo0MCwiOCI6MjQ4LCI5IjoyNDUsIjEwIjo5MCwiMTEiOjE5MSwiMTIiOjIwMiwiMTMiOjM0LCIxNCI6MTM0LCIxNSI6MjE0LCIxNiI6MTc1LCIxNyI6NDAsIjE4IjoxMSwiMTkiOjg4LCIyMCI6MTk5LCIyMSI6MjUyLCIyMiI6MTcsIjIzIjo3NSwiMjQiOjEyNCwiMjUiOjEyNSwiMjYiOjE4NywiMjciOjQsIjI4IjoxMzIsIjI5IjoxMCwiMzAiOjgyLCIzMSI6MTE2LCIzMiI6MTkzLCIzMyI6Nzl9LCJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljX2tleXMiOlt7ImlkIjoielEzc2hta2VNZmh1RHdOOGExS3YiLCJ0eXBlIjoiU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOCIsImp3ayI6eyJrdHkiOiJFQyIsImNydiI6InNlY3AyNTZrMSIsIngiOiJhYTZhNDVhM1lnRW1sYks2cjVwYmlCcDhkV2RVZTZ2bUJsWWhIS2dhWGRRIiwieSI6InZPR1A2cVVZMUt3cFk3VFpENzI5c2p3S2d0b0hPZ1NyY1pHMmFUSExQYWsifSwicHVycG9zZSI6WyJnZW5lcmFsIl19XX19XX0"
         }
      ]
   },
   "methodMetadata":{
      "recoveryCommitment":"EiDfGi474ij49Vq_yiKG1q8oC1jH_BFLfH27BIQKUnTBTw"
   }
}

When I would have assumed the following result being sufficient

{
   "@context":"https://www.w3.org/ns/did-resolution/v1",
   "didDocument":{
      "id":"did:ion:test:EiC0wVS3K9VLv-XZVL55i8Y4d1Hk1kJpeckfDXIu0nM17A",
      "@context":[
         "https://www.w3.org/ns/did/v1",
         {
            "@base":"did:ion:test:EiC0wVS3K9VLv-XZVL55i8Y4d1Hk1kJpeckfDXIu0nM17A"
         }
      ]
   },
   "methodMetadata":{
      "recoveryCommitment":"EiDfGi474ij49Vq_yiKG1q8oC1jH_BFLfH27BIQKUnTBTw"
   }
}
brentzundel commented 4 years ago

The group feels that better language around this topic should be added to the spec.

wyc commented 4 years ago

We could say that DID URLs MAY resolve to DID Documents for resolver-parsed parameters and fragments, but DID Documents MUST contain a DID in the id property. Does that sound right @OR13 @tplooker ?

peacekeeper commented 4 years ago

Per the current spec, the above did:ion Sidetree example is not a valid DID document, since the value of id MUST be a DID, not a DID URL (see section DID Subject in the spec). Personally, I don't think anything should be changed about this, except perhaps explain it a bit better.

OR13 commented 4 years ago

I agree, we should provide an example (not ION, but similar structure) and a note.

OR13 commented 4 years ago

Needs PR to add explicit warning about how DID params are dropped from the did document and may be present in resolver meta data.... needs PR.

csuwildcat commented 4 years ago

Assign me.

csuwildcat commented 4 years ago

Still need to generate PR for this, sorry for the delay :/

msporny commented 4 years ago

If we don't get a PR for this before we enter CR, this issue will be deferred to the next version of the specification. @csuwildcat, you have 30 days to generate a PR for this issue.

OR13 commented 4 years ago

I worry that folks don't realize how this impacts Verifiable Credentials....

this means that when you resolve:

"did:example:123?version-id=1#key-0"

and you get back a did document with verificationMethod:

"did:example:123#key-0"

You will need to do the following:

for JWS, you can just "find the public key bytes and check the signature.... "

for linked data proofs, you will need to apply post resolver middleware that "inserts the version" IF the original signature was over the version, OR you will have to be careful about checking the credential...

cc @dlongley

I worry that if we are not explicit here, one of the key features of the did spec will be implemented very differently by DID Methods, leading to broken verifiable credentials.

msporny commented 4 years ago

should didDocument.id === did:example:123?q1=1&q2=2 or did:example:123 ?

The second value... that is did:example:123 -- what am I missing?

The signed VC can have a verificationMethod of did:example:123?q1=1&q2=2 ... and that's what's signed over via the LD Signature... so when you dereference did:example:123?q1=1&q2=2 you get a DID Document that contains something with that ID in there... or a fragment identifier.

Perhaps there is some algorithm somewhere in some spec that is causing an issue here?

OR13 commented 4 years ago

@msporny its rather that there is no algorithm in ANY spec, that handles this properly... including the VC Data Model (does not elaborate on query parameters) and JWT/JWS (which does not understand dereferencing).

Best thing for developers would be a set of "Good" and "Bad" example using query params in the verification method and the vc data model.

msporny commented 4 years ago

Best thing for developers would be a set of "Good" and "Bad" example using query params in the verification method and the vc data model.

I'd be supportive of this.

The right place to do that might be the Linked Data Security specs (which is really unfortunate, because they are not normative in any way). We could put an appendix in the DID Core spec on the topic, it would be a bit weird and out of place, but at least it would be there. We could put it in the implementation guide, but I expect that thing won't be done by REC.

I think I'd rather document this in the Linked Data Proofs spec in the processing of the verificationMethod algorithm... but then went to look at the latest spec text: https://w3c-ccg.github.io/ld-proofs/#proof-verification-algorithm ... which is just awful, everything is terrible.

OR13 commented 4 years ago

I think this belongs in did core, since did core is defining dereferencing... we either define how parameters are or are not present in the resulting documents... or we stop trying to define dereferencing... the work needs to be done in did core, or its like saying we support HTTP GET, but don't define the response format.

peacekeeper commented 4 years ago

I think this belongs in did core, since did core is defining dereferencing

The idea was that DID Core would define abstract functions with the inputs and outputs of resolving and dereferencing, and that DID Resolution would define algorithms that implement those abstract functions, answering questions such as:

There is such an early algorithm in DID Resolution (see here), but admittedly this is not in sync with DID Core and needs to be updated.

OR13 commented 4 years ago

@peacekeeper regarding query parameters.... they are NOT allowed in the id field of a did document.

Are they allowed in the id field of a verificationMethod?

peacekeeper commented 4 years ago

Are they allowed in the id field of a verificationMethod?

@OR13 yes the id of a verification method could be a DID URL such as did:example:123?version-id=1#key-0.

The spec currently says:

The value of the id property for a verification method MUST be a URI.

Perhaps we should add the following?

The value of the id property for a verification method MUST be a URI and MAY be a DID URL.

Or even SHOULD be a DID URL?

OR13 commented 4 years ago

@dlongley @peacekeeper @msporny @dhh1128 @kdenhartog This is one of the most important parts of the whole specification.

It's the link between did core and all assertion formats that use identifiers, including JWT, VCs, ZKPs....

Its relevant too HTTP Signatures, DIDComm and many other areas....

Whatever rules we have here will shape how useful DIDs are in those other areas.

If we are saying that a verificationMethod.id MAY be a DID URL that contains query parameters, it raises questions about how they are related the DID URL that is input to dereferencing.

I think there are a number of assumptions developers will have, which I am not sure this WG as fully considered:

Dereferencing from aJWT.kid=did:example:123?version=52#primary-signing-key, I expect a VM with id=did:example:123?version=52#primary-signing-key to be present in the did document. (query params are reflected in verificationMethod id).

However, this is NOT ALLOWED per the spec today when combined with relative refs:

https://w3c.github.io/did-core/#relative-did-urls

didDocument.id cannot include the query params, so relative refs cannot be combined with query params of any format.

This kind of ambiguity is likely to cause serious security / interop issues for anyone using query params.

I would go so far as to say, its very dangerous for us to not have some concrete examples of VCs / JWTs issued from DID URLs in the spec, or language in the spec that clearly explains that they are not allowed.

dhh1128 commented 4 years ago

I'm not feeling confident that I grasp all the implications here. I think the sentence that I most need to understand is this one from Orie:

"didDocument.id cannot include the query params, so relative refs cannot be combined with query params of any format."

I THINK I'm totally comfortable with that, but I may be missing something.

My own plans have always been that the id property of anything in a DID document is either a pure DID (as in the case of didDocument.id), or is a relative reference like #key1. I have never intended for id properties to include parameters anywhere. But perhaps what Orie is pointing out is that the possibility for that has not been carefully eliminated by spec language, and because it exists, there is ambiguity? I admit that I would scratch my head a bit about what it would mean to try to combine parameters from DID URLs with fragments.

Or is there some use case for DIDComm where it is important to have DID URLs as the value of an id property somewhere?

OR13 commented 4 years ago

If you are using JSON-LD, the identifiers from the credential are expected to match the document returned by the resolver... JSON-LD has a whole section of the spec dedicated to handling "documentLoaders" of which DID resolvers are related... for JWTs or other ZKP formats, there is not always an assumption of "documentLoaders" and getting to "public key bytes / verification method details" may not be formally defined anywhere.... for example, OIDC defines this process for JWT id_token, but the same logic (using well known URLs) does not work for arbitrary JWTs.... its undefined behavior.

Essentially the DID Spec needs to comment on the following:

{
  "id": "did:key:zXwpDqA...",
  "verificationMethod": [
    {
      "id": "#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
      }
    }
  ],
...

vs

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
  ],
  "id": "did:key:zXwpDqA...", // query not allowed here....
  "verificationMethod": [
    {
      "id": "did:example:123?version=52#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
      }
    }
  ],
...

vs

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
   {
      "@base": "did:key:zXwpDqAV...?version=52"
    }
  ],
  "id": "did:key:zXwpDqA...", // query not allowed here....
  "verificationMethod": [
    {
      "id": "did:example:123?version=52#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
      }
    }
  ],
...

The reason being that the following URIs are all different, and signing over them / derefrencing them in different ways could lead to interop failures:

  1. did:example:123
  2. did:example:123#key-456
  3. did:example:123?version=0#key-456
  4. key-456

If your did document uses relative refs, verificationMethod.id can only be of the form: did:example:123?version=0#key-456 or #key-456

If you don't use relative refs, you can have the following:

{
  "id": "did:key:zXwpDqA...",
  "verificationMethod": [
    {
      "id": "did:key:zXwpDqA...#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": { ...}

    },
   {
      "id": "did:key:zXwpDqA...?version=52#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": { ...}

    },
   {
      "id": "did:key:zXwpDqA...?version=51#zXwpDq...",
      "type": "JsonWebKey2020",
      "controller": "did:key:zXwpDqAV1ME...",
      "publicKeyJwk": { ...}

    },
  ],
...

Since did:key:zXwpDqA...?version=51#zXwpDq... != did:key:zXwpDqA...?version=52#zXwpDq... != did:key:zXwpDqA...#zXwpDq...

The are technically unique...

The DID core spec already recommends using verificationMethod.id as the kid field in JOSE... in Linked Data Proof, the value in the proof is expected to be present in the resolved did document... for example:

"proof": {
    "type": "EcdsaSecp256k1Signature2019",
    "created": "2020-11-16T17:42:52Z",
    "verificationMethod": "did:example:123#key-0",
    "proofPurpose": "assertionMethod",
    "jws": "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..WBkq33Zs0dwzbqo-F66RUlVfMYWc-g8B3NANI5FULwR10fnYElJNI6K1wbqAuClVMGsEZAbwJX5jmeUPGbdvNA"
  }

"verificationMethod": "did:example:123#key-0",

and

"verificationMethod": "did:example:123?version=0#key-0",

Are different URIs....

if did:example:123?version=0#key-0 is not an id of a verificationMethod in the result of resolving did:example:123?version=0#key-0... the proof will not verify with without the verifier mutating the result of the resolution (tampering with the resolution response).

Similarly for JOSE, if the kid is did:example:123?version=0#key-0, but the DID Document only contains:

did:example:123#key-0

Or vice versa, how does the verifier know which public key bytes to use?

OR13 commented 4 years ago

cc @selfissued @csuwildcat this is also related to "DID / JOSE interoperability"... and the very poor usability of the "VC-JWT" format... since VC-JWT does not have "documentLoader" but also does not define a dereferencing algorithm for kid -> publicKeyBytes.

Sidetree JWS does not require kid to be present in the JWS header nor would requiring it along be sufficient, and subsequently, we have a whole section of the spec dedicated to explaining how to verify a JWS.... without clarity on this in DID Core, we will be inviting everyone to do the same everywhere else.

peacekeeper commented 4 years ago

Is this maybe the same problem as the long form / short form thing in Sidetree?

Could you do this:

{
  "id": "did:example:123",
  "sameAs": "did:example:123?version=0",
  "verificationMethod": [ {
      "id": "#key-0",
      "type": "JsonWebKey2020",
      "controller": "did:example:123",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}

Then dereferencing did:example:123?version=0#key-0 and did:example:123#key-0 would both "work"?

Or could you have a sameAs property on the verificationMethod level instead of the top level?

OR13 commented 4 years ago

@peacekeeper yes, its related...

"sameAs": "did:example:123?version=0",

This would imply you can merge the graphs, which if you rotated compromised keys out... that would be bad... also semantically, did subject at timestamp (-5 hours) and did subject at (now) are not really the "same thing" imo... raises questions on if the did subject can change, like did:example:123 => my dog, but then it dies and I get a new dog and use the same did.

In the case of sidetree long and short form, the did documents are actually the same, "except for the id / controller", whereas these would be very different did documents depending on version.

This is why equivalence properties and security need to be handled with care.

We don't want to say that an old (possibly compromised) set of keys are the same kind of authoritative as the current set.

Because version is defined, but can be handled differently by each did method, this becomes a potential area for security fail.

I think the behavior folks are looking for when they craft: did:example:123?version=52#key-73

Is a stable identifier for public key bytes at a moment in time.

Returning a did document at that moment in time with stable identifier for keys at that time satisfies this desire.

But its only possible if the did document DOES NOT use relative refs.... I imagine that will not be obvious to most developers, because its a function of the normative requirements for didDocument.id, verificationMethod.id and relative refs... in other words, you need to read all 3 sections carefully to realize that VCs can only be issued from DID Methods that support version and that DO NOT implement relative refs....

kdenhartog commented 4 years ago

So from my understanding of this issue, we need to do two things.

  1. update the language to adhere to @peacekeeper suggestion
  2. update implementations to explicitly use version parameter in the id fields of keys so that they can be properly identified without any semantic ambiguity.

Is that correct and all that's needed to close this issue @OR13 or would there be other orthogonal aspects unrelated to the use in the specific use cases brought up recently?

dhh1128 commented 4 years ago

But its only possible if the did document DOES NOT use relative refs

This is what I'm missing. I don't understand why. It seems to have something to do with embedding a version in a DID URL, and with some requirements of Linked Data Proofs that I don't know because I don't use them? Why would I ever want to reference a version of a DID doc from inside that same DID document? I see that as being useful only outside a DID document, to refer to things inside the doc at a particular version.

I apologize for asking what appears to be a remedial question, @OR13. You've tried really hard to explain this already, I can tell. But I think the amount of detail you've given is overwhelming the insight I'm supposed to be able to derive.

I want to name my keys "#key1", "#key2", and so forth (everything is relative except for the DID document's root id property). Further, the DID methods I use reject a possible usage that the spec allows, where #key1 can mean something different in version 1 than it does in version 2. Relative IDs are never reused. (I argued strenuously in favor of that possibility being eliminated across the board, but was voted down, so I can only speak for my own DID methods semantics there.) In that mental model, if #key1 is present in version 1 and version 2 and version 3 of the doc, then it is the same in all of them, and the relative reference is unambiguous. Perhaps the issue Orie is worried about occurs because some DID methods allow this sort of redefinition of a relative reference, and therefore require a version to disambiguate?

OR13 commented 4 years ago

@dhh1128 consider, a JWS or a linked data proof using kid or verificationMethod did:example:12#key-0

Valid

{
  "id": "did:example:123",
  "verificationMethod": [ {
      "id": "#key-0",
      "type": "JsonWebKey2020",
      "controller": "did:example:123",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}
{
  "id": "did:example:123",
  "verificationMethod": [ {
      "id": "did:example:123#key-0",
      "type": "JsonWebKey2020",
      "controller": "did:example:123",
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}

Now consider, a JWS or a linked data proof using kid or vm did:example:12?version=53#key-0

Valid

{
  "id": "did:example:123", // version is not allowed here
  "verificationMethod": [ {
      "id": "#key-0", // the strings do not match,  (this is a relative ref), but this is the correct key to use.
      "type": "JsonWebKey2020",
      "controller": "did:example:123", // version is not allowed here
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}
{
  "id": "did:example:123", // version is not allowed here
  "verificationMethod": [ {
      "id": "did:example:12?version=53#key-0", // strings match (this is not a relative ref)
      "type": "JsonWebKey2020",
      "controller": "did:example:123", // version is not allowed here
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}

Valid ?

{
  "id": "did:example:123", // version is not allowed here
  "verificationMethod": [ {
      "id": "?version=53#key-0", // strings do not match (this is a relative ref)
      "type": "JsonWebKey2020",
      "controller": "did:example:123", // version is not allowed here
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}

In this example "version" query param is dropped or preserved... this issue asks the question, how and when should query params be dropped / preserved.

The answer appears to be "up to the did method to decide"... the examples I have just shown demonstrate why thats dangerous, because all the examples are "valid" interpretation of did core today, and a verifier / developer would be super sad to have to support all of them.

There is a gap between the VC Data Model / JOSE and DID Core, and through that gap issues like this will rain down on the heads of sad developers :)

I'd love to be able to say something like:

verificationMethod.id MUST NOT contain DID URL path or query values...

or

verificationMethod.id MUST contain DID URL path and query if they are present in the original DID URL...

or

relativeRefs MUST NOT contain path and query

or

relativeRefs MUST contain path and query if they are present in the original DID URL...

Because these statements would reduce the possible ways developers can interpret DID Core.

TallTed commented 3 years ago

To my mind...

The underlying assumption is that query arguments impact the data which is returned when dereferencing any URL, including DIDs and DID URLs. Therefore, if a DID method, and hence any DID or DID URL based on that method, uses query arguments -- be those arguments &version or &q1 or &version-id or anything else -- then those query arguments are essential elements of the DIDs and DID URLs, and they MUST be preserved at all times.

Relative URLs found in any document/serialization -- which includes DID Documents and anything else that results from dereferencing a DID or DID URL -- MUST NOT be taken from that document in their relative form, because that is not how they are meant to be consumed, ever. #key-0 is meaningless on its own -- and moving it from one context (i.e., base) to another, while not guaranteed to lead to no value or to an incorrect value, is just as likely to as not. Such relative URLs MUST be expanded to their complete form, which would include any query arguments which were part of the dereferencing request (or in HTTP world, part of the Location header resulting from that dereferencing request), before use anywhere else for anything else.

DID Documents are not encountered as files in a filesystem -- though they may have been composed thus. They are encountered when dereferencing a DID and/or a DID URL. Any relative URLs therein become full URLs when they are encountered by scheme- and/or media-type-compliant tools.

Look at what happens when a relative link in an HTML document is clicked on within an HTML browser rendering of that HTML document. The URL you see in the location box is never the barebones relative URL, nor does it lose any arguments which were present in that location box before you clicked on the originally-relative link. The URL you find when you "View Source" is relative, because it's meant to be used in a context which is not the source; that context is the rendering you see in the browser. Relative URLs are used by authors to save typing, disk consumption, and transmission bandwidth; they are not meant to be taken out of the context of the document in which they're found and/or the protocol through which that document is consumed.

In other words, I believe relativeRefs are only meant to be relative when they're in a place which will turn them into absoluteRefs when they're put to use. All of that results in --

OR13 commented 3 years ago

@TallTed thanks for commenting on this!

I think I agree with your proposal.

consider the verificationMethod in a proof, with value:

did:example:123?version=53#key-0

This must be de-referencable to:

{
  "id": "did:example:123", // version is not allowed here
  "verificationMethod": [ {
      "id": "?version=53#key-0", // strings do not match (this is a relative ref)
      "type": "JsonWebKey2020",
      "controller": "did:example:123", // version is not allowed here
      "publicKeyJwk": {
        "kty": "EC",
        "crv": "P-256",
        "x": "5ZfvRFT4SXjCW2xbsQj6Yt3aYl9P3MnRLUaRSoWV5nA",
        "y": "adpC6bexW8SqYTQe_80dKT87mDIC3irPtT7smKR7btA"
  } ]
}

note that id and controller DO NOT have path and query, but verificationMethod.id does.

TallTed commented 3 years ago

@OR13 -

(I think you meant [emphasis mine], "note that id and verificationMethod.controller DO NOT have path and query, but verificationMethod.id does.")


First, it's important to remember that did:example:123?version=53#key-0 means "the fragment identified by key-0 in the DID Document that results from dereferencing the did:example:123?version=53 DID URL".

Also, that dereferencing the did:example:123?version=53 DID URL is expected to result in a different (perhaps entirely different!) DID Document than dereferencing the did:example:123?version=11 DID URL.

It may also be important to know which of the following DID URLs you get by resolving the did:example:123 DID --

-- and/or whether the ?version=## parameter is necessary to resolve the DID at all.


All that leads me to think that your --

  "id": "did:example:123", // version is not allowed here

-- should be --

  "id": "did:example:123?version=53", // all query parameters are likely required here

-- because the content of the DID Document will change somewhat with each version -- and may change entirely with variance in any other named query parameter. Maybe all query parameters must be preserved as top-level attributes, so --

  "id": "did:example:123", // version is not allowed here
  "version": "53" 

And all that makes me think that using some other random query parameter that has no inferred lexical significance would be better for this exploration than ?version=53. (Perhaps ?foo=53?)

It also seems worthwhile to discuss cases with 2 or even 3 parameters, as the complications inherent in such scenarios will help tease out otherwise easy-to-overlook issues.

Similarly, the case where the controller value is the same as the DID Subject is more difficult to understand (and I think will, over time, be substantially less common) than the cases where these values differ. For these reasons, I suggest that discussions proceed with the cases where these values differ until all is fully understood. Discussion/exploration of the less-common case where they are the same should then be a fairly trivial exercise.


Last for this comment -- I submit that using relativeRefs is not always optimal, even if technically possible. Once fully understood, of course, and especially when they are generated and processed mechanically, they can be an important optimization tool. Until then, they can be a substantial source of error and confusion. (Not least, because there are different rules about expansion of relativeRefs in HTTP-land and in JSON-land and in JSON-LD-land, not to mention other contexts. In JSON, relative URLs are expanded by simple concatenation. In JSON-LD, some relative URLs are expanded by simple concatenation, and others are expanded according to HTTP rules.)

TallTed commented 3 years ago

Missed one thing...

I said that "verificationMethod.id MUST contain DID URL path and query if they are present in the original DID URL" ... and "relativeRefs SHOULD NOT contain path nor query"

It appears that I should have also said that "verificationMethod.id SHOULD NOT be a relativeRef if path and/or query are present in the original DID URL", which I thought was implied by the previous two statements.

Perhaps, instead of "relativeRefs SHOULD NOT contain path nor query", I should have said "relativeRefs MUST NOT contain path nor query" which would have forced verificationMethod.id to be an absoluteRef based on the other MUSTs... but I'm not sure that the relativeRef restriction is really a MUST in all cases.

OR13 commented 3 years ago

@TallTed thank you so much more this... we have really been lacking this kind of discussion.

I am pinging a bunch of folks who need to review this: @msporny @jandrieu @dlongley @tplooker @kdenhartog @awoie @oed @dhh1128 @philarcher @selfissued @csuwildcat @talltree @peacekeeper

welcome to the thread folks!

  1. we need examples in did core that use path and query
  2. we need examples that use relative and absolute id, controller and verificationMethod.id

Having worked a lot with sidetree and linked data, I can say with confidence that relative URIs are a huge pain.

all over the code you will be checking for relative and converting to absolute, because you can only dereference from an absolute.

As a developer, this really sucks, and makes did methods like sidetree and transmute's did key implementation frustrating to use, even though the did documents are IMO more aesthetically pleasing without the string duplication.

I think we are unlikely to remove support for relative id values today, but its probably a good idea for us to explain them better, especially in the context of path and query.

AFAIK, the use of the service param is the only real example we have and it only applies to query... there are exactly 0 examples of using path today.

I propose we add this example regarding service which works, and explains some parts of this, but we will need others.... we will need at least one useful one for path.

{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:example:123456789abcdefghi",
  "authentication": [{
    "id": "#keys-1",
    "type": "Ed25519VerificationKey2018",
    "controller": "did:example:123456789abcdefghi",
    "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
  }],
  "service": [{
    "id":"#github",
    "serviceEndpoint": "https://raw.githubusercontent.com/OR13"
  }]
}
did:example:123456789abcdefghi?service=github&relativeRef=/dndx/3572fcd201a4667c46eda82ef2a3544a6d40f4c2/packages/did-dereferencer/mod.ts 

should deference to

https://raw.githubusercontent.com/OR13/dndx/3572fcd201a4667c46eda82ef2a3544a6d40f4c2/packages/did-dereferencer/mod.ts

peacekeeper commented 3 years ago

I think the last few comments mix up the following two topics (even though certain issues may apply to both):

peacekeeper commented 3 years ago

It may also be important to know which of the following DID URLs you get by resolving the did:example:123 DID --

* `did:example:123?version=53`

* `did:example:123`

* `did:example:123?version=latest`

Note that recently a DID document metadata property called version-id was added, which can indicate in the metadata the version of the the returned DID document (if the DID method supports it). Maybe this solves some of the challenges discussed in this issue?

OR13 commented 3 years ago

@peacekeeper sorry I mean for the examples to be relative and not relative and use query

I think having examples which use version and server with relative and absolute id values solves the "useful query examples" part, but no the path part... we still don't have a good example of how path should be used....

peacekeeper commented 3 years ago

there are exactly 0 examples of using path today.

I am aware of one use case where the use of path is at least being considered (not decided yet). In the did:indy method (which is currently being designed as a replacement for did:sov) it's possible that DID URLs with path will be used for identifying objects on a ledger such as credential definitions. An example could be did:indy:sovrin:CYQLsccvwhMTowprMjGjQ6/creddefs/4.

The original idea of DID URLs was to allow path, query, fragment in the same way as e.g. HTTP URLs have them, and that their use would not be limited by the DID Core spec, but left open for DID methods and DID controllers to use in any way they want.

philarcher commented 3 years ago

Much as I wish it were otherwise, I don't have the expertise to comment on the detailed discussion around some of this. However, I worry about sentences like

The underlying assumption is that query arguments impact the data which is returned when dereferencing any URL, including DIDs and DID URLs.

In HTTP at least, that assumption is unsafe. If I look up https://philarcher.org or https://philarcher.org?foo=bar I get exactly the same thing because there's nothing there to respond to the query. In other cases of course it does indeed change what's returned completely. https://www.google.com/search?q=orie+steele and https://www.google.com/search?q=phil+archer give very different results. Conceptually, this is because we have an identifier (https://google.com/search) and we're making a query against that. It's called a query string for a reason :-)

Again, I'm talking HTTP/Web here 'cos that's what I know and you all will be able to judge whether that maps 1-1 to the world of DIDs and DID Docs, but if it does, then you cannot make any assumption that the query string does or does not make a difference. It might, it might not.

A query is made against the identified thing, it's not part of the identifier.

dlongley commented 3 years ago

So I think much of the difficulty here is that the DID spec, quite intentionally, says this (emphasis mine):

The DID URL dereferencing function dereferences a DID URL into a resource with contents depending on the DID URL's components, including the DID method, method-specific identifier, path, query, and fragment. This process depends on DID resolution of the DID contained in the DID URL. The details of how this process is accomplished are outside the scope of this specification...

I also think there's a mistake in the spec that says this (again, emphasis mine):

A DID fragment is used as method-independent reference into a DID document or external resource. https://www.w3.org/TR/did-core/#fragment

That last bit is probably something we've failed to clean up and is inaccurate. If you want a fragment to be a reference to something in an external resource, it must be encoded in the relative-ref part of the DID URL. The DID fragment should always be a reference into the DID document and I think removing the above mistake could help here with some of the confusion.

Now, though the details of the DID resolution process are out of scope for the DID spec, my personal preference for how it would be defined in some other spec would be such that we could rely on a DID resolver to do the heavy lifting, rather than having things spill into the application that uses it. I don't want to deal with DID Documents that are coming back with/being rewritten with all kinds of different queries/paths in them because I requested DID URLs that reference different versions of the DID Document. Instead, it should be as if I'm running an operation in the past -- on what would have been a current version of the DID Document.

I'm speaking specifically about the "version" parameter here, but I expect other DID URL parameters to work similarly; we should not be "rewriting" the DID Document to match the query. Any rewriting we do is likely just going to need to be "undone" by the application using it.

For example, if a verification method is referenced somewhere by a DID URL, then an application should be able to use a DID resolver to ask it to resolve that verification method and the application shouldn't have to do any other data transformations based on whether the verification method is from the current DID Document or an older version. However, the application MUST care about whether the referenced verification method is from a version of the DID Document that the application requires, and it should be easy (and the default behavior, IMO) to configure a DID resolver to refuse to resolve old versions. So, the application does need to know the version (or that it is current), but it shouldn't have to deal with version parameters in every value it accesses, it should just "check it at the front door".

With respect to the Linked Data Proof use case, I think most current LD Proof software (and the DID resolvers used by it) doesn't yet support the version parameter and so this is presently a non-issue. Of course, software that actually requires old versions could be specially configured to allow them, because such software will, presumably, have in place the appropriate measures to deal with the fact that it may be interfacing with a verification method that has been revoked. I'm not even sure that referencing a verification method within an LD Proof using a version like that is the right way to go. It may depend on the use case; it may make more sense to put the DID URL with the version in it elsewhere or to make the resolver take it as a parameter based on simulating verifying the LD Proof in the past. But that's digressing.

So, my preference would be for a DID resolver to know how to resolve did:method:1234?version=100#keys-1 to get the verification method did:method:1234#keys-1 if it exists at that version and if you enable it to do so. Anyone who needs this behavior could use such a resolver whether it be for verifying a Linked Data Proof that is "frozen in time" (or was "valid at some point") or for any other purpose, perhaps having nothing to do with verification methods, but perhaps with services instead.

The resolver's behavior, in my view, should be to resolve the DID URL to a DID Document that represents the DID Document at version 100, which would not have ?version=100 in it anywhere. It would simply be a version of the DID Document at some specific point in time in the past. Then the DID fragment (#keys-1) would be used to dereference the data subject (verification method or service) identified by did:method:1234#keys-1. The DID fragment is always concatenated onto the end of the DID, which must not contain query/path, to produce the full identifier for what is referenced within the DID Document. Again, removing that "or external resource" from the DID fragment section would help here.

So, asking for this: did:method:1234?version=100#keys-1

Could result in the resolver getting this DID Document:

{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:method:1234",
  "authentication": [{
    "id": "did:method:1234#keys-1",
    "type": "Ed25519VerificationKey2018",
    "controller": "did:method:1234",
    "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
  }],
  "service": [{
    "id": "did:method:1234#github",
    "serviceEndpoint": "https://raw.githubusercontent.com/OR13"
  }]
}

In which it would then find the verification method identified by did:method:1234#keys-1 and return it to the caller.

Whereas asking for this: did:method:1234#keys-1

May instead result in the resolver getting this DID Document:

{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:method:1234",
  "authentication": [{
    "id": "did:method:1234#keys-2",
    "type": "Ed25519VerificationKey2018",
    "controller": "did:method:1234",
    "publicKeyBase58": "<something else>"
  }],
  "service": [{
    "id": "did:method:1234#github",
    "serviceEndpoint": "https://raw.githubusercontent.com/OR13"
  }]
}

And it not finding the thing (verification method) referenced, because the current version changed the authentication key.

The above should be the same whether the DID Document uses relative URLs for its id values or not.

Lastly, I do want to very strongly agree with this statement by @OR13,

Having worked a lot with sidetree and linked data, I can say with confidence that relative URIs are a huge pain.

all over the code you will be checking for relative and converting to absolute, because you can only dereference from an absolute.

As a developer, this really sucks, and makes did methods like sidetree and transmute's did key implementation frustrating to use, even though the did documents are IMO more aesthetically pleasing without the string duplication.

This is exactly the same experience I've had for years working with DID Documents. This is precisely why we asked that the spec NOT support relative URLs in DID Documents that are returned from resolvers. Supporting them, I believe, is an inversion in the priority of constituencies; developers should not have to deal with this stuff when any benefit it provides to a particular DID method can be hidden behind a DID resolver. Letting it spill into applications that are using DID resolvers is not worth any benefit that I've experienced; it's a net negative by far.

OR13 commented 3 years ago

@dlongley thanks for your detailed response, I hope everyone reads and understands it.

I have a couple assertions I want to make directly to you, and then I would love to try and address why they are problematic, and how we might fix them.

  1. did:method:1234?version=100#keys-1 will be seen as the verificationMethod for some proofs attached to VCs.

I believe hyperledger, ethereum and maybe even sidetree is doing this...and while i continue to object to it on the grounds that it makes the verifier's job harder and is fundamentally less secure... I think I will not convince them to rewrite their code.

  1. because of 1, the result of RESOLVE did:method:1234?version=100#keys-1 must contain a verificationMethod with this value as the ID.

If we try to return stuff with an id like did:method:1234#keys-1 off the shelf software will fail to verify it, and more importantly, the people who have implemented this won't follow our advice anyway.

They may not care about linked data, so they may not care that this is a problem, but it remains a problem, regardless, just like malformed html is still malformed, even if the page renders and is usable.

ASSERTION 1: If you want to support linked data proofs for a specific version of a DID Document, you MUST ensure that the verificationMethod of the proof can be de-referenced to an id in the DID Document.

ASSERTION 2: It is possible to support VC Linked Data Proofs with versioned URLS and absolute and relative refs, but only if you are smart enough to handle relative refs correctly... this means injecting @base or returning the full did document, so that the absolute id in the vc proof, can be matched to the result of dereferencing.

Assertion 3: The confusion over these parts of did core is related to a lack of consensus regarding revocation, verification and time. There are 2 perspectives, Position A: a credential is valid if it was issued by a key at a time that the key was valid, and that key has not been revoked since (this is the extra verifier work, check external registry or latest did document), Position B: a credential is valid if it was issued by a key that is CURRENTLY valid, and the credential has not been revoked.

I think @dlongley @msporny, myself and probably others would agree that Position B is safer, than Position A... but @dhh1128 @oed and others who have advocated for the use of version and clearly want to be allowed to have the signature verification succeed and still reject the credential based on the fact that they key was since revoked.

Position B is safe if the verifier does the extra work, which is not described in any spec (VC Data Model does not describe key revocation, nor does DID Core).

If the verifier does not do the extra work, a compromised key can be used to create fraudulent credentials, and any verifier will accept them.... it is loosely equivalent to preventing key rotation.

Because Position A and Position B are likely to remain forever, we should describe them both, pick a recommendation if we can, and explain how they are related to query params.

The lack of clarity regarding this aspect of verification using DIDs remains the largest security issue in the Spec, in my opinion.... I don't think we will succeed in eliminating A or B, and under the assumption of competent developers, both are safe... we should address this in the did core spec directly.

agropper commented 3 years ago

+1 Orie.

I would go further and say that we need to explicitly address anything that dereferences the content of a DID document AND that we need to explicitly consider the role of audit in the application of VCs and DIDs.

On Thu, Jan 7, 2021 at 11:30 AM Orie Steele notifications@github.com wrote:

@dlongley https://github.com/dlongley thanks for your detailed response, I hope everyone reads and understands it.

I have a couple assertions I want to make directly to you, and then I would love to try and address why they are problematic, and how we might fix them.

  1. did:method:1234?version=100#keys-1 will be seen as the verificationMethod for some proofs attached to VCs.

I believe hyperledger, ethereum and maybe even sidetree is doing this...and while i continue to object to it on the grounds that it makes the verifier's job harder and is fundamentally less secure... I think I will not convince them to rewrite their code.

  1. because of 1, the result of RESOLVE did:method:1234?version=100#keys-1 must contain a verificationMethod with this value as the ID.

If we try to drop return stuff with an id like did:method:1234#keys-1 off the shelf software will fail to verify it, and more importantly, the people who have implemented this won't follow our advice.

They may not care about linked data, so the may not care that this is a problem, but it remains a problem, regardless, just like malformed html is still malformed, even if the page renders and is usable.

ASSERTION 1: If you want to support linked data proofs for a specific version of a DID Document, you MUST ensure that the verificationMethod of the proof can be de-referenced to an id in the DID Document.

ASSERTION 2: It is possible to support VC Linked Data Proofs with versioned URLS and absolute and relative refs, but only if you are smart enough to handle relative refs correctly... this means injecting @base or returning the full did document, so that the absolute id in the vc proof, can be matched to the result of dereferencing.

Assertion 3: The confusion over these parts of did core is related to a lack of consensus regarding revocation, verification and time. There are 2 perspectives, Position A: a credential is valid if it was issued by a key at a time that the key was valid, and that key has not been revoked since (this is the extra verifier work, check external registry or latest did document), Position B: a credential is valid if it was issued by a key that is CURRENTLY valid, and the credential has not been revoked.

I think @dlongley https://github.com/dlongley @msporny https://github.com/msporny, myself and probably others would agree that Position B is safer, than Position A... but @dhh1128 https://github.com/dhh1128 @oed https://github.com/oed and others who have advocated for the use of version clearly want to be allowed to have the signature verification succeed and still reject the credential based on the fact that they key was since revoked.

Position B is safe if the verifier does the extra work, which is not described in any spec (VC Data Model does not describe key revocation, nor does DID Core).

If the verifier does not do the extra work, a compromised key can be used to create fraudulent credentials, and any verifier will accept them.... it is loosely equivalent to preventing key rotation.

Because Position A and Position B are likely to remain forever, we should describe them both, pick a recommendation if we can, and explain how they are related to query params.

The lack of clarity regarding this aspect of verification using DIDs remains the largest security issue in the Spec, in my opinion.... I don't think we will succeed in eliminating A or B, and under the assumption of competent developers, both are safe... we should address this in the did core spec directly.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/w3c/did-core/issues/337#issuecomment-756224896, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABB4YJVUV3RIKIYZWGDJYDSYXORLANCNFSM4OMT6FZA .

peacekeeper commented 3 years ago

If you want a fragment to be a reference to something in an external resource, it must be encoded in the relative-ref part of the DID URL. The DID fragment should always be a reference into the DID document

@dlongley I don't think we can require this.

Example 1 (based on your statement):

did:example:123?service=github&relative-ref=%2Fsomepath%3Fsomequery%23somefrag <-- encoded fragment

Example 2:

did:example:123?service=github&relative-ref=%2Fsomepath%3Fsomequery#somefrag <-- "real" fragment

I think both should be considered valid. If we prohibit Example 2, I think we would be violating standard URL dereferencing rules: First you dereference the URL without the fragment. The resulting is a representation of DID document or some other resource. Then you dereference the fragment, no matter what that resource is.

BTW, matrix parameters would solve this :)

peacekeeper commented 3 years ago

@dlongley says:

we should not be "rewriting" the DID Document to match the query.

@OR13 says:

the result of RESOLVE did:method:1234?version=100#keys-1 must contain a verificationMethod with this value as the ID.

I guess the resolved DID document could contain both, using alsoKnownAs/sameAs/equivalentId/etc., e.g.:

{
  "@context": "https://www.w3.org/ns/did/v1",
  "id": "did:method:1234",
  "authentication": [{
    "id": "did:method:1234#keys-1",
    "alsoKnownAs": [ "did:method:1234?version=100#keys-1" ],
    "type": "Ed25519VerificationKey2018",
    "controller": "did:method:1234",
    "publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
  }]
}

But this probably comes with its own set of problems, including that "off the shelf software will fail to verify it".

OR13 commented 3 years ago

Its an interesting thought, but I don't think alsoKnownAs helps here, it just makes things more complex.

I agree that fragment and encoded fragment are both valid in DID URLs.

TallTed commented 3 years ago

A DID client resolves a DID according to the DID method, to get a DID URL which (minus fragment segment) is then dereferenced by the DID client to get a representation of the DID Document from the DID server, within which the DID client may dereference the fragment segment (if any) from the DID URL.


In the so-called "encoded fragment" example from @peacekeeper, there is actually no fragment involved!

There is a very long query segment, that being all of ?service=github&relative-ref=%2Fsomepath%3Fsomequery%23somefrag starting with the query separator, ?.

did:example:123?service=github&relative-ref=%2Fsomepath%3Fsomequery%23somefrag, is being requested from the server by the client.

Yes, %23 is the URL encoded (a/k/a "percent encoded") version of the URL fragment separator, the # hash character, but by being so encoded, it is transformed! When dereferencing a URL, the fragment separator and the fragment identifier are stripped from that URL, either typically (e.g., most if not all HTTP/HTML browsers since day one) or mandatorially (e.g., HTTP 2 forbids its inclusion).

In this example, the DID URL query parameter relative-ref has the value %2Fsomepath%3Fsomequery%23somefrag, which is supposed to have some meaning to the resource processing the query -- and it might be that that meaning is "give me only the stuff identified by somefrag" but that is not the same as being a fragment identifier.

After the server responds with whatever, the client would use the fragment identifier (if any were present) to dereference the fragment from within the representation returned by the server. But again, there is no fragment identifier, so the client can perform no dereferencing of any fragment.

OR13 commented 3 years ago

related to https://github.com/w3c/did-core/issues/386, I propose we answer this with examples, and explain how they are related.

peacekeeper commented 3 years ago

In the so-called "encoded fragment" example from @peacekeeper, there is actually no fragment involved!

@TallTed I fully agree with this, thanks for clarifying! I also agree with you on your statements about stripping away the fragment during the dereferencing process, and that the fragment is dereferenced in a separate step after the primary resource has been dereferenced.

After the server responds with whatever, the client would use the fragment identifier ...

But I also want to point out that when talking about DID URL dereferencing, we typically avoid the terms "client" and "server", since those roles don't necessarily exist when dereferencing a DID URL. They are implementation-specific. The DID URL dereferencing process has no dependency on HTTP or DNS, and in some cases doesn't even require any network communication at all.

kdenhartog commented 3 years ago

After reading through this with a fresh set of eyes to the problem, I tend to prefer the direction that @dlongley suggested in this https://github.com/w3c/did-core/issues/337#issuecomment-755452327

However, I want to clarify that I'm understanding it properly.

@OR13 said here https://github.com/w3c/did-core/issues/337#issuecomment-756224896:

ASSERTION 1: If you want to support linked data proofs for a specific version of a DID Document, you MUST ensure that the verificationMethod of the proof can be de-referenced to an id in the DID Document.

I understand the intent of this to be that we don't want the resolver to be modifying the did document to accommodate for a variety of DID URLs being resolved. This I agree with. However, this statement is specifically about reference to another version. What if I wanted to reference to another DID Document entirely? In this case would it be an issue if I wanted to absoluteRef because it's a reference to an external resource rather than a relative reference?

TallTed commented 3 years ago

@peacekeeper --

Sure, we can avoid using "client" and "server", but something must be used to label the entities serving similar roles, even if these labels are not generic enough. (They're not HTTP-specific, even if that's often the easiest frame to use as an example.)

You didn't provide any alternative terms; do you have suggestions? "DID Resolver" would seem to be the agent that delivers the DID URL, but maybe it's the agent that is resolving the DID to get to the DID URL? "DID Document Requester" would seem to be clearly enough the agent that is dereferencing the DID URL to get the DID Document, but what is the agent that provides the DID Document?

(These labels are important, and not easy to settle on. It took months if not years to settle on Issuer, Verifier, Subject, and Holder for Verifiable Credentials -- if those can even be considered to be fully settled, now.)


Strictly speaking, HTTP doesn't require DNS, either; http://127.0.0.1:1111/, for instance, is perfectly valid, as is http://[::1]:443/.

peacekeeper commented 3 years ago

@TallTed

In my mind it's like this:

Both could be quite simple or quite complex. Both could consist of multiple hard- and software components (even in a classic client/server architecture), but they don't have to.

I'd be fine with continuing to use the term "client" to designate the thing that invokes the DID resolution and/or DID URL dereferencing functions. But we agreed at some point that we don't consider the "client" to participate in performing those processes. In other words, in the case of DID URLs the "client" does NOT do dereferencing, that is done entirely by a "DID URL dereferencer" (which can itself be a local process). If we want to discuss this further, we should probabaly raise a separate issue.

Two references that could be relevant here:

TallTed commented 3 years ago

My https://github.com/w3c/did-core/pull/544#issuecomment-759532512 may be more relevant here than there...

OR13 commented 3 years ago

I wrote some tests trying to make sense of didDocument.id and verificationMethod.id as they relate to path and query params in verifiable credentials...

I am very concerned by the results, most of the working examples are illegal according to did core spec today, and I could not produce a single "working legal example" of path and query params in a did document that worked with verifiable credentials and that was legal wrt normative requirements in did core today.... and also worked off the shelf with JSON-LD.

The cause of this is assumptions in linked data apis, (like frame) about the relationship between didDocument.id, verificationMethod.id and controller.... we are walking into a really nasty set of interoperability failures based on the current language in did core.

take a look at these examples:

https://github.com/OR13/did-params-and-you/tree/master/examples

In particular, I believe that @dhh1128 @oed wished to use version-id with credentials... I have included tests that work, but are illegal wrt did core.... https://github.com/OR13/did-params-and-you/blob/master/examples/case-2-illegal-working.json

The reason this is illegal is that didDocument.id MUST be a DID, NOT a DID URL... this normative requirement essentially prevents VCs from relying on query params in the verification method currently.... in a way that works for both JSON-LD and JSON....

Based on these tests, I am not sure how exactly to resolve this issue, but I am leaning towards recommending normative changes to did core, to make the working examples legal.

@dlongley @msporny I am concerned that the JSON-LD and non JSON-LD community are not working together wrt normative requirements in did core that impact verifiable credentials....

The did core spec should not be making valid linked data stuff illegal... especially if the non linked data community is going to just ignore the linked data side of the spec anyway... the normative requirements of did core need to work for both sides, and we should not be encouraging many slightly different ways of handling things like query and path.

PROPOSED CHANGES:

For the best example of something that "works" but is "not legal", see this:

{
  "http://example.gov/credentials/3732": {
    "@context": [
      "https://www.w3.org/2018/credentials/v1"
    ],
    "id": "http://example.gov/credentials/3732",
    "type": [
      "VerifiableCredential",
      "UniversityDegreeCredential"
    ],
    "issuer": {
      "id": "did:example:123?version-id=77d66171-b290-489c-abf1-95ae10725201"
    },
    "issuanceDate": "2020-03-10T04:24:12.164Z",
    "credentialSubject": {
      "id": "did:example:456"
    },
    "proof": {
      "type": "Ed25519Signature2018",
      "created": "2021-01-16T20:37:54.785Z",
      "jws": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..bDCoLFkyGIrcLLtiUZ-1tW30T6A2uV0j3R0FEtGK-qT3QEuWuvafd8cGfgp1rd7wAFARSrR50hCPS-W0ko1jBw",
      "proofPurpose": "assertionMethod",
      "verificationMethod": "did:example:123?version-id=77d66171-b290-489c-abf1-95ae10725201#credential-issuance-key"
    }
  },
  "did:example:123?version-id=77d66171-b290-489c-abf1-95ae10725201": {
    "@context": [
      "https://www.w3.org/ns/did/v1"
    ],
    "id": "did:example:123?version-id=77d66171-b290-489c-abf1-95ae10725201",
    "assertionMethod": [
      {
        "id": "#credential-issuance-key",
        "type": "Ed25519VerificationKey2018",
        "controller": "",
        "publicKeyBase58": "g9X6bPg6ZKWvc24zgqMhrXerMXoY878KqPxfBiEVLsJ"
      }
    ]
  }
}

Without the proposed changes, existing credential libraries will be incapable of supporting normatively legal did documents that use query parameters...

BTW, while it might seem like this is "only a problem for JSON-LD"... its actually a problem for anyone trying to use did core with verifiable credentials.

for VC-JWT there is no defined algorithm for verification method dereferencing (they don't have documentLoaders or frame)... which means that there is even more optionality in terms of how those folks might handle this issue.... if they read the spec today, they will 100% create solutions that work for JWT that are not legal JSON-LD... and then later encounter sever pain as they try and get their did method to "support both camps"....