Sphereon-Opensource / OID4VC-demo

Demo for OID4VC, containing a configurable agent, OID4VCI Issuer and demopage as well as SIOPv2 and OID4VP RPs with demo page
Apache License 2.0
17 stars 8 forks source link

Error when trying to acquire a credential. #63

Closed ismapolis closed 12 months ago

ismapolis commented 1 year ago

Hello, I have been testing the OID4VCI libraries with the issuer front-end available at: https://ssi.sphereon.com/demo/issuer. I have been trying to request a demo credential (Sphereon Guest Credential) following the guide about OpenID4VCI Client. Every step goes smoothly until I try to acquire the credential. The error I am encountering is the following: Error: Retrieving a credential from https://ssi.sphereon.com/pf3/credentials for issuer https://ssi.sphereon.com/pf3 failed with status: 500 I'll let the code I am using in case I am forgetting something.

const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' })
const metadata = await client.retrieveServerMetadata()

//2. Adquire acces token from authorization server endpoint
const accessToken = await client.acquireAccessToken({})

//3. Create DID needed for later proof of possession
const { keys, didDocument } = await did.jwk.generate({
  type: 'secp256k1', // 'P-256', 'P-384', 'X25519', 'secp256k1'
  accept: 'application/did+json',
  secureRandom: () => {
    return crypto.randomBytes(32)
  },
})
const edPrivateKey = await jose.importJWK(keys[0].privateKeyJwk)

async function signCallback(args: Jwt, kid?: string): Promise<string> {
  if (!args.payload.aud) {
    throw Error('aud required')
  } else if (!kid) {
    throw Error('kid required')
  }
  return await new jose.SignJWT({ ...args.payload })
    .setProtectedHeader({ alg: args.header.alg })
    .setIssuedAt(+new Date())
    .setIssuer(kid)
    .setAudience(args.payload.aud)
    .setExpirationTime('2h')
    .sign(edPrivateKey)
}

const callbacks: ProofOfPossessionCallbacks<DIDDocument> = {
  signCallback: signCallback,
}

const credentialResponse = await client.acquireCredentials({
  credentialTypes: 'GuestCredential',
  proofCallbacks: callbacks,
  format: 'jwt_vc_json',
  alg: Alg.ES256K,
  kid: didDocument.verificationMethod[0].id,
  jti: v4(),
})
console.log(credentialResponse.credential)
ismapolis commented 1 year ago

The URI value I am using is generated when a credential option is selected and a QR code is generated. I simply copy the URL and insert it into the code. The URL always looks like this: openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22wzUetZE7ytnS8n4ZwbLHwG%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22GuestCredential%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fssi.sphereon.com%2Fpf3%22%7D

nklomp commented 12 months ago

Hi @ismapolis

I have been looking into your issue. You can see your code incorporated into a test here: https://github.com/Sphereon-Opensource/OID4VCI/blob/9f4eca6130a045e20999506c1ded96abddcd9f41/packages/client/lib/__tests__/SphereonE2E.spec.test.ts#L119 The above code also automatically creates a new offer, so you do not have to copy paste anything ;)

First of all probably good for you to know for next time is that you can include the following line to get more information about any errors and/or payloads:

debug.enable('*') 
// or debug.enable('sphereon:*')

Then you would get this response payload on your console:

sphereon:openid4vci:http error status: 500, body:
  sphereon:openid4vci:http {"error":"invalid_request","error_description":"invalid_jwt: JWT not valid yet (issued in the future) iat: 1699049651145"}

The issue is that in your code you are using +new Date() for the issuedAt value, but that should be devided by 1000 (seconds vs milliseconds) for JWTs. If you just call it with setIssuedAt() it will automatically use current date

After fixing that some small other errors occured which had to do with some values in the JWT sign callback missing. You need to provide a kid header as well as set the correct typ ('openid4vci-proof+jwt')