w3c / did-core

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

DIDs and JOSE: publicKey.id and publicKey.publicKeyJwk.kid #259

Closed OR13 closed 4 years ago

OR13 commented 4 years ago

There are many JWS/JWT implementations out there that don't have any kid... so we need publicKey.id to be a separate thing... I think we have established consensus on this topic, thats not what this issue is about.

For DID Documents that contain verificationMethods that use publicKeyJwk For example:

const key = {
  id: "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  type: "JwsVerificationKey2020",
  controller: "did:web:identity.foundation",
  publicKeyJwk: {
    crv: "Ed25519",
    x: "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ",
    kty: "OKP",
    kid:
      "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  },
  privateKeyJwk: {
    crv: "Ed25519",
    x: "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ",
    d: "tP7VWE16yMQWUO2G250yvoevfbfxY25GjHglTP3ZOyU",
    kty: "OKP",
    kid:
      "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  },
};

The following language in did core would be exceedingly helpful / fix a major problem linking JWTs and DIDs...

If kid is present in publicKeyJwk its value MUST equal ${verificationMethod.controller}#${rfc7638(verificationMethod.publicKeyJwk)}.

https://tools.ietf.org/html/rfc7638

per jose https://tools.ietf.org/html/rfc7517#section-4.5 kid can be anything... so this is legal... if we don't prefix the kid with the controller... we have no guarantee of a resolvable verificationMethod for JWT/JWS... which cripples them from a VC perspective.

Why?

https://www.w3.org/TR/vc-data-model/#jwt-encoding

For backward compatibility with JWT processors, the following JWT-registered claim names MUST be used instead of, or in addition to, their respective standard verifiable credential counterparts...

When this language is combined with https://tools.ietf.org/html/rfc7517#section-4.5

The result is potentially valid VC-JWT with no way to lookup the key used to sign it.... because JOSE allows kid to be anything....

This section of text is non-normative:

https://www.w3.org/TR/vc-data-model/#example-27-jwt-header-of-a-jwt-based-verifiable-credential-using-jws-as-a-proof-non-normative

So basically, the VC-JWT format is not defined, because key discovery was out of scope for the VC Data Model... yet, key discovery for LD-Proofs is defined, because of the use of assertionMethod.

https://www.w3.org/TR/vc-data-model/#proofs-signatures-0

Dereferencing a public key URL reveals information about the controller of the key, which can be checked against the issuer of the credential.

The fact that similar language was not added for VC-JWT is imo, the its greatest failing w.r.t. use with DIDs....

VC-JWT offers this confusing non normative and completely incompatible example:

https://www.w3.org/TR/vc-data-model/#example-28-jwt-payload-of-a-jwt-based-verifiable-credential-using-jws-as-a-proof-non-normative

"iss": "https://example.com/keys/foo.jwk",

This implies that VC-JWTs are issued by keys, whereas LD-Proofs are issued by identifiers.... there is no well-known open id configuration, no jwks url to check for kids that could be anything... this differs from oidc where issuer is a domain, not a reference to a specific key... also...

https://example.com/keys/foo.jwk -> jwk

did:web:identity.foundation#Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY-tA4A -> did document...

One uses a fragment, the other does not.... they point to completely different resources...

The mapping for VC-JWT is not defined.... and IMO, VC JWT is totally broken because of this.

This proposal would allow jose libraries to produce valid VC JWTs, with no modification...if they normally support kid it will be inserted correctly.

Some objections:

  1. I don't want to see duplication!

There are many implementations of JWT/JWS in the wild today that use publicKey.id for kid.... so either we duplicate things like this, or did core says that UPort / Mattr / Transmute / anyone using non JWK encoded keys to produce JWS / JWT are wrong and we make JWK the only key format for DIDs.... this will never happen, please recognize that it will never happen, and that this duplication is what allows those implementations to be valid / continue to work, while resolving ambiguity for the people who come later and only support JWK.

  1. I don't care about key representations other than JWK

Per https://tools.ietf.org/html/rfc7517#section-4.5 kid can be whatever anybody wants... so we can be more restrictive here if we want to... there are a million reasons why its a good idea to be more restrictive here... allowing kid to be "whatever" was a huge mistake by the jose spec authors IMO.

  1. I don't care about the VC Data Model

This is one of the largest problems with it... its so bad, I would say that this issue makes the VC-JWT representation NOT USABLE.... this kind of ambiguity is interop destroying... since we can't fix it in the vc data model... we should consider fixing it here....

Alternatives:

VC JWT could have mandated that iss not be used to identify a specific key... and then could have added language similar to open id / oauth... for looking up a key used for a JWT by using iss and kid and well known URIs...

VC JWT could have mandated that if iss is a did, then kid is a fragment or that if kid contains a did, then iss is that did.

VC JWT could have mandated what I'm proposing in this issue.

OR13 commented 4 years ago

@msporny @selfissued heads up, I have changed my view of being more restrictive of publicKeyJwk based on experience working with JOSE and the VC Data Model. IMO VC-JWT is dead on arrival as a usable credential format for DIDS without the change outlined in this issue...

OR13 commented 4 years ago

For example, when trying to create VC-JWT Domain Linkage Credential for the Well Known DID Configuration Spec (DIF / MS / Transmute / Mattr)... this is what we are looking at:

const jose = require("jose");

const key = {
  id: "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  type: "JwsVerificationKey2020",
  controller: "did:web:identity.foundation",
  publicKeyJwk: {
    crv: "Ed25519",
    x: "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ",
    kty: "OKP",
    kid:
      "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  },
  privateKeyJwk: {
    crv: "Ed25519",
    x: "VCpo2LMLhn6iWku8MKvSLg2ZAoC-nlOyPVQaO3FxVeQ",
    d: "tP7VWE16yMQWUO2G250yvoevfbfxY25GjHglTP3ZOyU",
    kty: "OKP",
    kid:
      "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
  },
};

const credentialSubject = require("./credentialSubject.json");
// const credentialSubject = {
//   id: "did:web:identity.foundation",
//   domain: "identity.foundation",
// };

describe("jwt-domain-linkage-credential", () => {
  it("sign and verify JWT", () => {
    const jwtPayload = {
      sub: credentialSubject.id,
      iss: credentialSubject.id,
      nbf: 1541493724,
      iat: 1541493724,
      exp: 2573029723,
      nonce: "660!6345FSer",
      vc: {
        "@context": [
          "https://www.w3.org/2018/credentials/v1",
          "https://www.w3.org/2018/credentials/examples/v1",
        ],
        type: ["VerifiableCredential", "DomainLinkageCredential"],
        credentialSubject,
      },
    };
    const jwt = jose.JWT.sign(jwtPayload, jose.JWK.asKey(key.privateKeyJwk), {
      iat: false, // do not overrite iat
      kid: true, // pushes kid to JWT Header,
      // beware that kid can anything...
      // an incredibily terrible property of jose...
    });
    const { header, payload, signature } = jose.JWT.verify(
      jwt,
      jose.JWK.asKey(key.publicKeyJwk),
      {
        complete: true,
      }
    );
    expect(header).toEqual({
      kid:
        "did:web:identity.foundation#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A",
      alg: "EdDSA",
    });
    expect(payload).toEqual(jwtPayload);
    expect(signature).toEqual(
      "ly4pu1jJbLShaSkKgS68nPBSZjzkA-3dlfuOI6_GV8CGDx6-pYg2npiBNi3DqKelRndUM6e25agp36-63el2DA"
    );
  });
});
OR13 commented 4 years ago

I suppose we could double down on the ambiguity, and say that kid can be whatever, and just leave the VC Data Model fixing for another WG....

its possible to resolve a key for a JWT if iss is a did and kid is a publicKey.id... and since there is no normative language in vc data model about his, maybe its best that we just explicitly note that kid can be anything, and it might be totally useless property, or contain meta data or pii, or other harmful values... per the JOSE spec.

csuwildcat commented 4 years ago

Related to your latest reply, @OR13, I would rather just accept the kid optionality as it is described in the JWK/JWT spec, and make a specific note about it in the DID spec so people realize that the publicKey.id is the overarching construct that covers all variants.

OR13 commented 4 years ago

That sounds good to me... I think the did core spec has to mention what happend in VC-JWT though, because it provides examples that people are building implementations from...

Something informational to the effect of:

kid can be anything, publicKey.id is often used as the kid value in JWS/JWT/JWE... however there are also cases where kid is not prefixed with a DID. In such cases, implementers need to know how to resolve the kid identifier on a case by case basis:

  1. example using iss

  2. example using sidetree jws

  3. example from well-known did configuration

This will provide clarity, while not actually reducing developer burden, and we can try again when we get VC Data Model 2.0

OR13 commented 4 years ago

Suggest we close this issue, we have spec language that addressed this.

msporny commented 4 years ago

The spec currently says:

The value of the id property MAY be structured as a compound key. This is especially useful for integrating with existing key management systems and key formats such as JWK. It is RECOMMENDED that JWK kid values are set to the public key fingerprint. It is RECOMMENDED that verification methods that use JWKs to represent their public keys utilize the value of kid as their fragment identifier.

Agree with @OR13, we can close this.

brentzundel commented 4 years ago

No comments raising objections to closing this issue