Giter VIP home page Giter VIP logo

oid4vci's Introduction


Sphereon
OpenID for Verifiable Credential Issuance - Client and Issuer

CI codecov NPM Version

IMPORTANT the packages are still in an early development stage, which means that breaking changes are to be expected

Background

This is a mono-repository with a client and issuer pacakge to request and receive Verifiable Credentials using the OpenID for Verifiable Credential Issuance ( OpenID4VCI) specification for receiving Verifiable Credentials as a holder/subject.

OpenID4VCI defines an API designated as Credential Endpoint that is used to issue verifiable credentials and corresponding OAuth 2.0 based authorization mechanisms (see [RFC6749]) that a Wallet uses to obtain authorization to receive verifiable credentials. W3C formats as well as other Credential formats are supported. This allows existing OAuth 2.0 deployments and OpenID Connect OPs (see [OpenID.Core]) to extend their service and become Credential Issuers. It also allows new applications built using Verifiable Credentials to utilize OAuth 2.0 as an integration and interoperability layer. This package provides holder/wallet support to interact with OpenID4VCI capable Issuer systems.

In addition to the client and issuer, there is also a common package, which has all the types and payloads shared between the client and issuer.

Packages

There are 2 main packages in this mono-repository

OpenID for VCI Client

The OpenID4VCI client is typically used in wallet type of applications, where the user is receiving the credential(s). More info can be found in the client README

OpenID for VCI Issuer

The OpenID4VCI issuer is used in issuer type applications, where an organization is issuing the credential(s). More info can be found in the issuer README. Please note that the Issuer is a library. It has some examples on how to run it with REST endpoints. If you are however looking for a full solution we suggest our SSI SDK or the demo

Flows

The spec lists 2 flows:

Authorized Code Flow

This flow is supported but might need more work, so you might run into issues trying to use it.

Pre-authorized Code Flow

The pre-authorized code flow assumes that the user is using an out of bound mechanism outside the issuance flow to authenticate first.

The below diagram shows the steps involved in the pre-authorized code flow. Note that inner wallet functionalities (like saving VCs) are out of scope for this library. Also This library doesn't include any functionalities of a VC Issuer Flow diagram

oid4vci's People

Contributors

berendsliedrecht avatar brummos avatar btencatesphereon avatar github-actions[bot] avatar hrehman-sphereon avatar karimstekelenburg avatar maikel-maas avatar mobskuchen avatar nklomp avatar rkreutzer avatar sanderpostma avatar sksadjad avatar sphereon-ci avatar timoglastra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

oid4vci's Issues

Token endpoint resolution

if (!token_endpoint && issuerMetadata.authorization_server) {
debug(
`Issuer ${issuer} OID4VCI metadata has separate authorization_server ${issuerMetadata.authorization_server} that contains the token endpoint`,
);
// Crossword uses this to separate the AS metadata. We fail when not found, since we now have no way of getting the token endpoint
const response: OpenIDResponse<OAuth2ASMetadata> = await this.retrieveWellknown(
issuerMetadata.authorization_server,
WellKnownEndpoints.OAUTH_AS,
{
errorOnNotFound: true,
},
);
token_endpoint = response.successBody?.token_endpoint;
}

Hi,

I believe this is a bug in the logic. The latest draft of OIDC4VCI states

authorization_server: OPTIONAL. Identifier of the OAuth 2.0 Authorization Server (as defined in [[RFC8414](https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#RFC8414)]) the Credential Issuer relies on for authorization. If this element is omitted, the entity providing the Credential Issuer is also acting as the AS, i.e., the Credential Issuer's identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server metadata as per [[RFC8414](https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#RFC8414)].

The second sentence states, that if authorization_server param is missing, then the credential_issuer value shall be used as AS, ie the metadata shall be retrieved from credential_issuer endpont .well-known/openid-configuration.

`determineSpecVersionFromURI` error

While trying to use the library, I am getting and error from the determineSpecVersionFromURI function.

Error:

Error: Invalid param. Some keys have been used from version: 1011 version while 'pre-authorized_code' is used from version: 1009

The error occurs with the following URI:
openid-credential-offer://credential_offer?credential_issuer=http%3A%2F%2F127.0.0.1%3A3003&credentials%5B0%5D=GmCredential&grants%5Burn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%5D%5Bpre-authorized_code%5D=38ea2b4a-ea85-4d52-8e59-937b52218a89

I think this line of the code is the problem, because both the VER_1_0_09 and VER_1_0_11 use the pre-authorized_code.

Remove support for versions below draft 11

When developing on this library, over time it has lost some of the simplicity due to several formats and protocol versions being supported.

We have targeted Draft 8 before, but now are mainly working on Draft 11 and possibly newer drafts (maybe 19 or another one). To improve maintainability, I propose support for Draft 8 and 9 is removed. This will allow us to remove a lot of transformation code, and also simplify a lot of small issues currently encountered with this library.

Thoughts on removing support for drafts below 11? People still wanting to support these older drafts could stick to an older version of the library for now

Accept RSA Signing Algorithms

Hi ๐Ÿ‘‹

In our goal of supporting OIDC4VCI/OIDC4VP protocols in the Gaia-X ecosystem we based ourselves on your OID4VCI lib which is very easy to get up to speed with thanks to the documentation and examples.

Our Use Case

Gaia-X Clearing Houses currently need an EV SSL certificate in order to be able to issue compliance verifiable credentials. Those certificates are mostly delivered with RSA signing algorithms and are costly so we wanted to try using those with your library to avoid having to ask members to buy new EV SSL certificates with EdDSA, ES256 or ES256K signatures.

The Issue

We noticed that you implemented a closed list of algorithms with the Alg enum and that a check is done to make sure the credential request proof uses a supported algorithm. The following error is raised :

image

I have tried adding the PS256 algorithm we use to the Alg enum and everything works fine ๐Ÿ‘

Thoughts

As jwtVerifyCallback is the method that's depending on the signing algorithm and that this callback is defined by the developer using the library, hence its responsibility, wouldn't it be possible to remove this check and just leave it to the implementer ?

If not, would it be possible to add widely used algorithms to the Alg enum or even leave the implementing developer define it in the VcIssuer metadata ?

It would be my pleasure to exchange with you about this ๐Ÿ˜Š

cNonce is not checked for expiration

I see that the cNonce used in a request is not checked for expiration.

What is checked is whether the credential request jwt iat (which is client-generated so not really trustable) is within the token expiration.

As I understand it, the token expiration for the access token expiration, and so instead of verifying the credential request jwt against the token expiration time, we should check it against the cNonce expiration time, to check if the cNonce used in the request hasn't expired yet.

Is that correct?

Leverage zod (or similar library) for validation

The validation within this library is mainly done manually. Because there's a lot of models and types, this can result in a lot of manual validation logic.

We've been adopting zod into our stack, and it has been working very well for us. You define a zod object, which defines the validation logic, and based on that you can infer the typescript type (as a simple type).

This way you can easily validate every interface you have in your library. It does add some overhead as you need to validate, but I don't think it will be very significant, and it does allow to clean up a lot of the validation code.

You can use things like refine to have very special validation logic, and I think this would enforce separating validation logic from the actual implementation side of the spec.

An example with some of the credential offer types

import { z } from 'zod'

const zJwtVcJsonOfferFormat = z.object({
  format: z.literal('jwt_vc_json'),
  types: z.array(z.string())
})

const zLdpVcOfferFormat = z.object({
  format: z.literal('ldp_vc'),
  "@context": z.array(z.string().url()),
  types: z.array(z.string())
})

const zCredentialOfferFormat = z.discriminatedUnion("format", [zJwtVcJsonOfferFormat, zLdpVcOfferFormat])

const zCredentialOfferPayload = z.object({
  credential_issuer: z.string().url(),
  credentials: z.array(z.union([z.string(), zCredentialOfferFormat])).nonempty()
})

const zCredentialOffer = z.union([
  z.object({
    credential_offer_uri: z.string().url(),
  }),
  z.object({
    credential_offer: zCredentialOfferPayload
  })
])

type CredentialOfferPayload = z.infer<typeof zCredentialOfferPayload>
type CredentialOffer = z.infer<typeof zCredentialOffer>


const results = [
  zCredentialOffer.safeParse({
    credential_offer_uri: 'https://google.com'
  }), // valid

  zCredentialOffer.safeParse({
    credential_offer: {
      credential_issuer: 'https://google.com',
      credentials: []
    } satisfies CredentialOfferPayload
  }), // invalid, credentials must have at least one entry

  zCredentialOffer.safeParse({
    credential_offer: {
      credential_issuer: 'https://google.com',
      credentials: [{
        format: 'ldp_vc',
        "@context": ["https://google.com"],
        types: ["VerifiableCredential"]
      }]
    } satisfies CredentialOfferPayload
  }), // valid

  zCredentialOffer.safeParse({
    credential_offer: {
      credential_issuer: 'https://google.com',
      credentials: [{
        format: 'jwt_vc_json',
        "@context": ["https://google.com"],
        types: ["VerifiableCredential"]
      }]
    } satisfies CredentialOfferPayload
  }) // "@context" is not defiend for jwt_vc_json. If we enable .strict() on the object it will error, but by default it will remove unknown properties
] 

console.log(JSON.stringify(results, null, 2))

This results in the following output:

[
  {
    "success": true,
    "data": {
      "credential_offer_uri": "https://google.com/"
    }
  },
  {
    "success": false,
    "error": {
      "issues": [
        {
          "code": "too_small",
          "minimum": 1,
          "type": "array",
          "inclusive": true,
          "exact": false,
          "message": "Array must contain at least 1 element(s)",
          "path": [
            "credential_offer",
            "credentials"
          ]
        }
      ],
      "name": "ZodError"
    }
  },
  {
    "success": true,
    "data": {
      "credential_offer": {
        "credential_issuer": "https://google.com/",
        "credentials": [
          {
            "format": "ldp_vc",
            "@context": [
              "https://google.com/"
            ],
            "types": [
              "VerifiableCredential"
            ]
          }
        ]
      }
    }
  },
  {
    "success": true,
    "data": {
      "credential_offer": {
        "credential_issuer": "https://google.com/",
        "credentials": [
          {
            "format": "jwt_vc_json",
            "types": [
              "VerifiableCredential"
            ]
          }
        ]
      }
    }
  }
]

Here's a codesandbox with the code: https://codesandbox.io/s/typescript-playground-export-forked-nrwgg4

Mattr Labs v11 issuer throws 400 on credential request

As brought up in #54 the new Mattrlabs V11 issuer doesn't seem to work with current VCI client.

The issue appears to be occuring in the Credential Endpoint

A new E2E test is created to track this issue.

It throws the following error with a HTTP 400:

{"code":"BadRequest","message":"Validation Error","details":[{"msg":"Invalid value","param":"type","location":"body"}]}

The error hints at the type param in the body. It could be that the issuer is still expecting a v8/v9 type in there. This has since moved to a types array.

I also noticed that the access token does not contain a c_nonce value, meaning we cannot set a nonce value in the JWT for the Credential Request. The spec does mention that this value is required in the request. Which then makes the remark about the c_nonce inconsistent, because the only way for a pre-auth flow to set the nonce is to actually get a c_nonce from the auth endpoint.

Additional payloads:

Note the c_nonce (and optional expiration of the nonce) missing
Token response:

{
      "access_token": "SMyM_X7aZpIad5sCEgkAtT0xT3JPyNwzRt6Lf4rW9o5",
      "expires_in": 3600,
      "scope": "OpenBadgeCredential",
      "token_type": "Bearer"
}

Credential Request. Note it is using the types array from v11 instead of the previous type value from v8

{
  "types": [
    "OpenBadgeCredential"
  ],
  "format": "ldp_vc",
  "proof": {
    "proof_type": "jwt",
    "jwt": "eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVkRFNBIiwia2lkIjoiZGlkOmtleTp6Nk1raTVad1pLTjFkQlFwcmZKVGlrVXZrRHhySGlqaWlRbmdrV3ZpTUY1Z3cySHYjejZNa2k1WndaS04xZEJRcHJmSlRpa1V2a0R4ckhpamlpUW5na1d2aU1GNWd3Mkh2In0.eyJhdWQiOiJodHRwczovL2xhdW5jaHBhZC52aWkuZWxlY3Ryb24ubWF0dHJsYWJzLmlvIiwiaWF0IjoxNjkxNzEzOTY2LCJleHAiOjE2OTE3MjExNjZ9.V09pI-L09VxoTpfEk8blmUDqojpqNkjdIcMSdwI8CBfjy-17rZsOLtKlq_SazOChoswK0CS1p_XYjb5lyT4JAg"
  }
}

The JWT header:

{
  "typ": "openid4vci-proof+jwt",
  "alg": "EdDSA",
  "kid": "did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv"
}

The JWT payload, which does not contain the required nonce value, because of no c_nonce provided in the token response

{
  "aud": "https://launchpad.vii.electron.mattrlabs.io",
  "iat": 1691713966,
  "exp": 1691721166
}

Specific reason why JSON-LD issuance is not supported?

There is a check in the VC issuer that explicitly marks JSON-LD as not being supported yet.

Is there a reason this is the case? By making the following change everything seems to work:

class VcIssuer {
             let preAuthorizedCode;
             let issuerState;
             try {
-                if (credentialRequest.format !== 'jwt_vc_json' && credentialRequest.format !== 'jwt_vc_json-ld' && credentialRequest.format !== 'vc+sd-jwt') {
+                if (credentialRequest.format !== 'jwt_vc_json' && credentialRequest.format !== 'jwt_vc_json-ld' && credentialRequest.format !== 'vc+sd-jwt' && credentialRequest.format !== 'ldp_vc') {
                     throw Error(`Format ${credentialRequest.format} not supported yet`);
                 }
                 else if (typeof this._jwtVerifyCallback !== 'function' && typeof jwtVerifyCallback !== 'function') {

So now I'm wondering if there's any checks missing for JSON-LD? It seems the request is properly validated, and the credentialSubject is set.

Link to spec that is no longer compatible with this library

Hello! It seems like the specification that is linked to in the README has gone through a number of breaking changes and is no longer compatible with this version of the library.
For example, the "issuance initiation endpoint" has been renamed to the "Credential Offer Endpoint" , and there are also a number of changes in the JSON. I wasn't able to find the version of the spec you guys were using and wondered if you had a link.

Updated Spec

`getIssuerFromCredentialOfferPayload` error

While trying to use the library, I am getting and error from the getIssuerFromCredentialOfferPayload function.

Error:

Error: can't retrieve metadata from CredentialOfferRequest. No issuer field is present

The error occurs with the following URI:
openid-credential-offer://credential_offer?credential_issuer=http%3A%2F%2F127.0.0.1%3A3003&credentials%5B0%5D=GmCredential&grants%5Burn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%5D%5Bpre-authorized_code%5D=38ea2b4a-ea85-4d52-8e59-937b52218a89

I think this line of the code is the problem.

Wrong `content-type` is set for `acquireAccessToken`

I came across an issue when trying to acquire the access token (by running OpenID4VCIClient.acquireAccessToken()).

I'm currently testing my setup against MATTR's launchpad. When I try to run OpenID4VCIClient.acquireAccessToken(), I'm getting a 400 response code with the following error message: only application/x-www-form-urlencoded content-type bodies are supported on POST /token.

After monitoring my outbound traffic I found that content-type was set to text/plain;charset=UTF-8. When I intercept the outgoing request and change the content-type to application/x-www-form-urlencoded the request succeeds and everything works as expected.

After some debugging, I can't seem to find the problem in this codebase. HttpUtils.formPost seems to set the content-type correctly... I'm not sure, but it could be an issue with cross-fetch.

I will continue looking into this but wanted to post it here in case anyone else runs into the same problem.

Love the API and solid typing by the way, nice job! ๐Ÿš€

Access token not verified in the credential endpoint implementation

When looking at the credential endpoint implementation in the issuer-rest package, I couldn't find any code related to the validation of the access token generated in the access token endpoint.

This endpoint should verify the bearer authorization token passed in the header.

I might be missing where this is happening, so in that case, please point me to the file where this is handled (I'm looking for some util functions I may be able to re-use for the Credo endpoint implementation)

A few type issues from Draft 13

Hi again,

so I moved a bit forward with my tests and it seems the latest types (v11) do not match current draft 13:


'@context': ICredentialContextType[];

Now the "types" is "type".

https://github.com/Sphereon-Opensource/OID4VCI/blame/6680088e6cc4c6dc9c7b05732acb7437ca4ddbfe/packages/common/lib/types/Generic.types.ts#L125
The credentialRequest is now united for both JOSE and LDP cases (as well as other types).

https://openid.bitbucket.io/connect/openid-4-verifiable-credential-issuance-1_0.html#name-w3c-verifiable-credentials

I know, it is pretty annoying with these unstable drafts breaking compatibility with each step, but are there plans for draft13 support?

Separate generic format definitions and the format specific interfaces

Currently the code is all a bit intertwined for different formats. This worked okay, but with different formats having different structures, it's become a bit hard to manage. This has mainly come up with adding support for the SD-JWT format, and that uses credential_definition.type , while JWT VC uses types (top level and plural), and JSON-LD uses credentialDefinition.types (camelCase and plural).

Should there have been more consistency between these types? Definitely! but it shows that we shouldn't assume the format specific fields between formats will be similar, and thus to improve maintainability of this library, I think all format specific handling should be separated form the general protocol implementation.

Thus we have the base offer/request etc.. interfaces and handling, and then for each format you need to define the specific offer/request/issuer metadata interfaces, as well as some functions that can e.g. verify a request, or transform an offer of that specific format into a request. This could be an interface that is implemented with some common methods.

This would allow adding new formats without needing to hardcode them in this library, and would make it easier to see what is base OID4VCI code, and what is custom format specific logic

Thoughts on this?

`credential_offer_uri=` hosted credential url should be uri encoded in the credential offer

Currently it is generated as e.g.:

openid-credential-offer://?credential_offer_uri=https://3767-217-123-18-26.ngrok-free.app/oid4vci/e451c49f-1186-4fe4-816d-a942151dfd59/offers/b1b39c88-6003-464f-bc1b-1f164fa45d59

While I think it should be:

openid-credential-offer://?credential_offer_uri=https%3A%2F%2F3767-217-123-18-26.ngrok-free.app%2Foid4vci%2Fe451c49f-1186-4fe4-816d-a942151dfd59%2Foffers%2Fb1b39c88-6003-464f-bc1b-1f164fa45d59

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.