npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@meeco/sd-jwt-vc

v1.3.0

Published

SD-JWT VC implementation in typescript

Downloads

271

Readme

npm npm

SD-JWT-VC

This is an implementation of SD-JWT VC (I-D version 01) in Typescript. It provides a higher-level interface on top of the @meeco/sd-jwt library to create the compliant SD-JWT VCs.

Installation

npm install @meeco/sd-jwt-vc

Usage

The library exposes three classes

Issuer

This is a TypeScript class that represents an issuer of Verifiable Credentials (VCs) that can create Signed and Disclosed JWTs (SD JWTs) for VCs.

Usage

To use the Issuer class, you need to create an instance of it by passing in a signer configuration object and a hasher configuration object. signer configuration callback function will be used to sign the SD JWTs and hasher configuration callback function will be used to hash the disclosued claims in the SD JWTs.

Here's an example:

import { createHash } from 'crypto';
import { JWTHeaderParameters, JWTPayload, KeyLike, SignJWT, exportJWK, generateKeyPair } from 'jose';
import { DisclosureFrame, Hasher, SDJWTPayload, Signer, base64encode } from '@meeco/sd-jwt';
import {
  CreateSDJWTPayload,
  HasherConfig,
  Issuer,
  SignerConfig,
  VCClaims,
  defaultHashAlgorithm,
  supportedAlgorithm,
} from '@meeco/sd-jwt-vc';

const hasherCallbackFn = function (alg: string = defaultHashAlgorithm): Hasher {
  return (data: string): string => {
    const digest = createHash(alg).update(data).digest();
    return base64encode(digest);
  };
};

const signerCallbackFn = function (privateKey: Uint8Array | KeyLike): Signer {
  return (protectedHeader: JWTHeaderParameters, payload: JWTPayload): Promise<string> => {
    return (await new SignJWT(payload).setProtectedHeader(protectedHeader).sign(privateKey)).split('.').pop();
  };
};

async function main() {
  const keyPair = await generateKeyPair(supportedAlgorithm.EdDSA);

  const hasher: HasherConfig = {
    alg: 'sha256',
    callback: hasherCallbackFn('sha256'),
  };
  const signer: SignerConfig = {
    alg: supportedAlgorithm.EdDSA,
    callback: signerCallbackFn(keyPair.privateKey),
  };

  const issuer = new Issuer(signer, hasher);
}

main();
  • singner: The signer configuration object. It contains the following properties:
    • alg: The algorithm to use for signing the SD JWTs. Must be one of the supported algorithms ('EdDSA', 'ES256', 'ES256K', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'RS256', 'RS384', 'RS512').
    • callback: The callback function that will be used to sign the SD JWTs. It must be a function that takes a string and returns a string.
  • hasher: The hasher configuration object. It contains the following properties:
    • alg: The algorithm to use for hashing the SD JWTs. Must be one of the available algorithms supported by OpenSSL.
    • callback: The callback function that will be used to hash the SD JWTs. It must be a function that takes a string and returns a string.

createVCSDJWT

Once you have an instance of the Issuer class, you can use it to create SD JWTs for VCs. Here's an example:

async function main() {
 ...
 ...

  const holderPublicKey = await exportJWK(keyPair.publicKey);

  const payload: CreateSDJWTPayload = {
    iat: Math.floor(Date.now() / 1000),
    cnf: {
      jwk: holderPublicKey,
    },
    iss: 'https://valid.issuer.url',
  };

  const vcClaims: VCClaims = {
    type: 'VerifiableCredential',
    status: {
      idx: 'statusIndex',
      uri: 'https://valid.status.url',
    },
    person: {
      name: 'test person',
      age: 25,
    },
  };

  const sdVCClaimsDisclosureFrame: DisclosureFrame = { person: { _sd: ['name', 'age'] } };

  const result = await issuer.createVCSDJWT(vcClaims, payload, sdVCClaimsDisclosureFrame);
  console.log(result);
}

main();

This will create an SD JWT for the given VC claims and SD JWT payload for given disclosure frame.

Holder

This is a TypeScript class that represents a holder of Verifiable Credentials (VCs) that can verify Signed and Present SD JWT VC's with Key Binding to Verifier.

Usage

To use the Holder class, you need to create an instance of it by passing in a Holder Signer configuration object and Hasher function resolver. Here's an example:

import { KeyBindingVerifier, Signer, decodeJWT, base64encode } from '@meeco/sd-jwt';
import { JWK, JWTHeaderParameters, JWTPayload, KeyLike, SignJWT, importJWK, jwtVerify } from 'jose';
import { Holder, SignerConfig, supportedAlgorithm } from '@meeco/sd-jwt-vc';
import { createHash } from 'crypto';

const hasherFnResolver = (alg: string) => {
  const hasher = (data: string): string => {
    const digest = createHash(alg).update(data).digest();
    return base64encode(digest);
  };

  return Promise.resolve(hasher);
};

const signerCallbackFn = function (privateKey: Uint8Array | KeyLike): Signer {
  return (protectedHeader: JWTHeaderParameters, payload: JWTPayload): Promise<string> => {
    return (await new SignJWT(payload).setProtectedHeader(protectedHeader).sign(privateKey)).split('.').pop();
  };
};

async function main() {
  const _publicJwk = {
    kty: 'EC',
    x: 'rH7OlmHqdpNOR2P28S7uroxAGk1321Nsgxgp4x_Piew',
    y: 'WGCOJmA7nTsXP9Az_mtNy0jT7mdMCmStTfSO4DjRsSg',
    crv: 'P-256',
  };
  const privateKey = {
    kty: 'EC',
    x: 'rH7OlmHqdpNOR2P28S7uroxAGk1321Nsgxgp4x_Piew',
    y: 'WGCOJmA7nTsXP9Az_mtNy0jT7mdMCmStTfSO4DjRsSg',
    crv: 'P-256',
    d: '9Ie2xvzUdQBGCjT9ktsZYGzwG4hOWea-zvCQSQSWJxk',
  };

  const pk = await importJWK(privateKey);

  const signer: SignerConfig = {
    alg: supportedAlgorithm.ES256,
    callback: signerCallbackFn(pk),
  };
  const holder = new Holder(signer, hasherFnResolver);
}

main();

presentVCSDJWT

Once you have an instance of the Holder class, you can use it to present SD JWTs for VCs. Here's an example:


const keyBindingVerifierCallbackFn = function (): KeyBindingVerifier {
  return async (kbjwt: string, holderJWK: JWK) => {
    const { header } = decodeJWT(kbjwt);

    if (!Object.values(supportedAlgorithm).includes(header.alg as supportedAlgorithm)) {
      throw new SDJWTVCError('unsupported algorithm');
    }

    const holderKey = await importJWK(holderJWK, header.alg);
    const verifiedKbJWT = await jwtVerify(kbjwt, holderKey);
    return !!verifiedKbJWT;
  };
};

async function main() {
 ...
 ...

  const issuedSDJWT =
    'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpYXQiOjE2OTU2ODI0MDg4NTcsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJ4Ijoickg3T2xtSHFkcE5PUjJQMjhTN3Vyb3hBR2sxMzIxTnNneGdwNHhfUGlldyIsInkiOiJXR0NPSm1BN25Uc1hQOUF6X210TnkwalQ3bWRNQ21TdFRmU080RGpSc1NnIiwiY3J2IjoiUC0yNTYifX0sImlzcyI6Imh0dHBzOi8vdmFsaWQuaXNzdWVyLnVybCIsInR5cGUiOiJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsInN0YXR1cyI6eyJpZHgiOiJzdGF0dXNJbmRleCIsInVyaSI6Imh0dHBzOi8vdmFsaWQuc3RhdHVzLnVybCJ9LCJwZXJzb24iOnsiX3NkIjpbImNRbzBUTTdfZEZXb2djcUpUTlJPeGJUTnI1T0VaakNWUHNlVVBVN0ROa3ciLCJZY3BHVTNKTDFvS0NoOXY4VjAwQmxWLTQtZTFWN1h0U1BvYUtra2RuZG1BIl19fQ.iPmq7Fv-pxS5NgTpH5xUarz6uG1MIphHy4q5mWdLBJRfp6ER2eG306WeHhCBoDzrYURgWZiEySnTEBDbD2HfCA~WyJNcEFKRDhBWVBQaEJhT0tNIiwibmFtZSIsInRlc3QgcGVyc29uIl0~WyJJbFl3RkV5WDlLSFVIU1NFIiwiYWdlIiwyNV0~';

  const disclosureList = [
    {
      key: 'name',
      value: 'test person',
      disclosure: 'WyJNcEFKRDhBWVBQaEJhT0tNIiwibmFtZSIsInRlc3QgcGVyc29uIl0',
    },
  ];

  const nonceFromVerifier = 'nIdBbNgRqCXBl8YOkfVdg==';

  const { vcSDJWTWithkeyBindingJWT } = await holder.presentVCSDJWT(issuedSDJWT, disclosureList, {
    nonce: nonceFromVerifier,
    audience: 'https://valid.verifier.url',
    keyBindingVerifyCallbackFn: keyBindingVerifierCallbackFn(),
  });

  console.log(vcSDJWTWithkeyBindingJWT);
}

main();

This will create an SD JWT VC with Key Binding JWT. The holder can then send this JWT to the verifier.

Verifier

This is a TypeScript class that represents a verifier of Verifiable Credentials (VCs) that can verify Signed and Disclosed SD JWT VC's with Key Binding to Holder.

Usage

To use the Verifier class, you need to create an instance of it and call the verifyVCSDJWT method on it. verifyVCSDJWT method takes the following parameters:

  • vcSDJWTWithkeyBindingJWT: The SD JWT VC with Key Binding JWT that was sent by the holder.
  • verifierCallbackFn: The callback function that will be used to verify the SD JWT VC with Key Binding JWT. It must be a function that takes a string and returns a boolean.
  • hasherCallbackFn: The callback function that will be used to hash the disclosued claims in the SD JWTs. It must be a function that takes a string and returns a string.
  • kbVeriferCallbackFn: The callback function that will be used to verify the key binding in the SD JWT VC with Key Binding JWT. It must be a function that takes a string and returns a boolean.

Here's an example:

import { Hasher, KeyBindingVerifier, base64encode, decodeJWT } from '@meeco/sd-jwt';
import { createHash } from 'crypto';
import { JWK, KeyLike, importJWK, jwtVerify } from 'jose';
import { Verifier, defaultHashAlgorithm, supportedAlgorithm } from '@meeco/sd-jwt-vc';

function verifierCallbackFn(publicKey: Uint8Array | KeyLike) {
  return async (jwt: string): Promise<boolean> => {
    const verifiedKbJWT = await jwtVerify(jwt, publicKey);
    return !!verifiedKbJWT;
  };
}

function hasherCallbackFn(alg: string = defaultHashAlgorithm): Hasher {
  return (data: string): string => {
    const digest = createHash(alg).update(data).digest();
    return base64encode(digest);
  };
}

function kbVeriferCallbackFn(expectedAud: string, expectedNonce: string, expectedSdHash: string): KeyBindingVerifier {
  return async (kbjwt: string, holderJWK: JWK) => {
    const { header, payload } = decodeJWT(kbjwt);

    if (expectedAud || expectedNonce || expectedSdHash) {
      if (payload.aud !== expectedAud) {
        throw new SDJWTVCError('aud mismatch');
      }
      if (payload.nonce !== expectedNonce) {
        throw new SDJWTVCError('nonce mismatch');
      }
      if (payload.sd_hash !== expectedSdHash) {
        throw new SDJWTVCError('sd_hash mismatch');
      }
    }

    if (!Object.values(supportedAlgorithm).includes(header.alg as supportedAlgorithm)) {
      throw new SDJWTVCError('unsupported algorithm');
    }

    const holderKey = await importJWK(holderJWK, header.alg);
    const verifiedKbJWT = await jwtVerify(kbjwt, holderKey);
    return !!verifiedKbJWT;
  };
}

async function main() {
  const verifier = new Verifier();
  const { vcSDJWTWithkeyBindingJWT, nonce } = {
    vcSDJWTWithkeyBindingJWT:
      'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpYXQiOjE2OTU2ODI0MDg4NTcsImNuZiI6eyJqd2siOnsia3R5IjoiRUMiLCJ4Ijoickg3T2xtSHFkcE5PUjJQMjhTN3Vyb3hBR2sxMzIxTnNneGdwNHhfUGlldyIsInkiOiJXR0NPSm1BN25Uc1hQOUF6X210TnkwalQ3bWRNQ21TdFRmU080RGpSc1NnIiwiY3J2IjoiUC0yNTYifX0sImlzcyI6Imh0dHBzOi8vdmFsaWQuaXNzdWVyLnVybCIsInR5cGUiOiJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsInN0YXR1cyI6eyJpZHgiOiJzdGF0dXNJbmRleCIsInVyaSI6Imh0dHBzOi8vdmFsaWQuc3RhdHVzLnVybCJ9LCJwZXJzb24iOnsiX3NkIjpbImNRbzBUTTdfZEZXb2djcUpUTlJPeGJUTnI1T0VaakNWUHNlVVBVN0ROa3ciLCJZY3BHVTNKTDFvS0NoOXY4VjAwQmxWLTQtZTFWN1h0U1BvYUtra2RuZG1BIl19fQ.iPmq7Fv-pxS5NgTpH5xUarz6uG1MIphHy4q5mWdLBJRfp6ER2eG306WeHhCBoDzrYURgWZiEySnTEBDbD2HfCA~WyJNcEFKRDhBWVBQaEJhT0tNIiwibmFtZSIsInRlc3QgcGVyc29uIl0~WyJJbFl3RkV5WDlLSFVIU1NFIiwiYWdlIiwyNV0~eyJ0eXAiOiJrYitqd3QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3ZhbGlkLnZlcmlmaWVyLnVybCIsIm5vbmNlIjoibklkQmJOZ1JxQ1hCbDhZT2tmVmRnPT0iLCJzZF9oYXNoIjoiTHdvOXZaMHc5SlVkZFlNdEVrc3JVYWc4TnRtY05JNGdFT3JhbzVYT1R6SSIsImlhdCI6MTcwNzE0NzYxNjk3MX0._rdKs3oVlxu6rGtbiBxP69Ammlc4OV6IPvQa9EVI6JUis3Vf5xOofS7xkJDeM5Q8rg00_vQqyQ21eYapyvLMSA',
    nonce: 'nIdBbNgRqCXBl8YOkfVdg==',
  };

  const issuerPubKey = await importJWK({
    crv: 'Ed25519',
    x: 'rc0lLGwZ7qsLvHsCUcd84iGz3-MaKUumZP03JlJjLAs',
    kty: 'OKP',
  });

  const vcSDJWTWithoutKeyBinding: string = vcSDJWTWithkeyBindingJWT.slice(
    0,
    vcSDJWTWithkeyBindingJWT.lastIndexOf('~') + 1,
  );
  const hasher: Hasher = hasherCallbackFn(defaultHashAlgorithm);
  const sdJwtHash: string = hasher(vcSDJWTWithoutKeyBinding);

  const result = await verifier.verifyVCSDJWT(
    vcSDJWTWithkeyBindingJWT,
    verifierCallbackFn(issuerPubKey),
    hasherCallbackFn(defaultHashAlgorithm),
    kbVeriferCallbackFn('https://valid.verifier.url', nonce, sdJwtHash),
  );
  console.log(result);
}