Skip to content

Commit

Permalink
feat: add new package for sd-jwt-vc
Browse files Browse the repository at this point in the history
Signed-off-by: Mirko Mollik <mirko.mollik@fit.fraunhofer.de>
  • Loading branch information
cre8 committed Mar 8, 2024
1 parent 642c7ee commit ed99188
Show file tree
Hide file tree
Showing 29 changed files with 1,160 additions and 132 deletions.
58 changes: 13 additions & 45 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,22 @@ import {
KB_JWT_TYP,
SDJWTCompact,
SDJWTConfig,
SD_JWT_TYP,
} from '@sd-jwt/types';

export * from './sdjwt';
export * from './kbjwt';
export * from './jwt';
export * from './decoy';

export interface SdJwtVcPayload {
// The Issuer of the Verifiable Credential. The value of iss MUST be a URI. See [RFC7519] for more information.
iss: string;
// The time of issuance of the Verifiable Credential. See [RFC7519] for more information.
iat: number;
// OPTIONAL. The time before which the Verifiable Credential MUST NOT be accepted before validating. See [RFC7519] for more information.
nbf?: number;
//OPTIONAL. The expiry time of the Verifiable Credential after which the Verifiable Credential is no longer valid. See [RFC7519] for more information.
exp?: number;
// REQUIRED when Cryptographic Key Binding is to be supported. Contains the confirmation method as defined in [RFC7800]. It is RECOMMENDED that this contains a JWK as defined in Section 3.2 of [RFC7800]. For Cryptographic Key Binding, the Key Binding JWT in the Combined Format for Presentation MUST be signed by the key identified in this claim.
cnf?: unknown;
//REQUIRED. The type of the Verifiable Credential, e.g., https://credentials.example.com/identity_credential, as defined in Section 3.2.2.1.1.
vct: string;
// OPTIONAL. The information on how to read the status of the Verifiable Credential. See [I-D.looker-oauth-jwt-cwt-status-list] for more information.
status?: unknown;

//The identifier of the Subject of the Verifiable Credential. The Issuer MAY use it to provide the Subject identifier known by the Issuer. There is no requirement for a binding to exist between sub and cnf claims.
sub?: string;

export interface SDJwtPayload {
// more entries
[key: string]: unknown;
}

export class SDJwtInstance {
export abstract class SDJwtInstance<ExtendedPayload extends SDJwtPayload> {
//header type
protected abstract type: string;

public static DEFAULT_hashAlg = 'sha-256';

private userConfig: SDJWTConfig = {};
Expand Down Expand Up @@ -85,7 +69,7 @@ export class SDJwtInstance {
return jwt.verify(this.userConfig.verifier);
}

public async issue<Payload extends SdJwtVcPayload>(
public async issue<Payload extends ExtendedPayload>(
payload: Payload,
disclosureFrame?: DisclosureFrame<Payload>,
options?: {
Expand All @@ -104,28 +88,8 @@ export class SDJwtInstance {
throw new SDJWTException('sign alogrithm not specified');
}

//validate disclosureFrame according to https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-01.html#section-3.2.2.2
if (
disclosureFrame?._sd &&
Array.isArray(disclosureFrame._sd) &&
disclosureFrame._sd.length > 0
) {
const reservedNames = [
'iss',
'iat',
'nbf',
'exp',
'cnf',
'vct',
'status',
];
// check if there is any reserved names in the disclosureFrame._sd array
const reservedNamesInDisclosureFrame = (
disclosureFrame._sd as string[]
).filter((key) => reservedNames.includes(key));
if (reservedNamesInDisclosureFrame.length > 0) {
throw new SDJWTException('Cannot disclose protected field');
}
if (disclosureFrame) {
this.validateReservedFields<Payload>(disclosureFrame);
}

const hasher = this.userConfig.hasher;
Expand All @@ -141,7 +105,7 @@ export class SDJwtInstance {
const OptionHeader = options?.header ?? {};
const CustomHeader = this.userConfig.omitTyp
? OptionHeader
: { typ: SD_JWT_TYP, ...OptionHeader };
: { typ: this.type, ...OptionHeader };
const header = { ...CustomHeader, alg };
const jwt = new Jwt({
header,
Expand All @@ -160,6 +124,10 @@ export class SDJwtInstance {
return sdJwt.encodeSDJwt();
}

protected abstract validateReservedFields<T extends ExtendedPayload>(
disclosureFrame: DisclosureFrame<T>,
): void;

public async present(
encodedSDJwt: string,
presentationKeys?: string[],
Expand Down
66 changes: 26 additions & 40 deletions packages/core/src/test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { SDJwtInstance } from '../index';
import { Signer, Verifier } from '@sd-jwt/types';
import { SDJwtInstance, SDJwtPayload } from '../index';
import { DisclosureFrame, Signer, Verifier } from '@sd-jwt/types';
import Crypto from 'node:crypto';
import { describe, expect, test } from 'vitest';
import { digest, generateSalt } from '@sd-jwt/crypto-nodejs';
import { SDJWTException } from '@sd-jwt/utils';

export class TestInstance extends SDJwtInstance<SDJwtPayload> {
protected type = 'sd-jwt';

protected validateReservedFields(
disclosureFrame: DisclosureFrame<SDJwtPayload>,
): void {
return;
}
}

export const createSignerVerifier = () => {
const { privateKey, publicKey } = Crypto.generateKeyPairSync('ed25519');
Expand All @@ -24,13 +33,13 @@ export const createSignerVerifier = () => {

describe('index', () => {
test('create', async () => {
const sdjwt = new SDJwtInstance();
const sdjwt = new TestInstance();
expect(sdjwt).toBeDefined();
});

test('kbJwt', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
signAlg: 'EdDSA',
verifier,
Expand Down Expand Up @@ -69,7 +78,7 @@ describe('index', () => {

test('issue', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
signAlg: 'EdDSA',
verifier,
Expand Down Expand Up @@ -103,7 +112,7 @@ describe('index', () => {
);
};

const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
signAlg: 'EdDSA',
verifier: failedverifier,
Expand Down Expand Up @@ -141,7 +150,7 @@ describe('index', () => {
Buffer.from(sig, 'base64url'),
);
};
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
signAlg: 'EdDSA',
verifier,
Expand Down Expand Up @@ -184,7 +193,7 @@ describe('index', () => {

test('verify with kbJwt', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
signAlg: 'EdDSA',
verifier,
Expand Down Expand Up @@ -223,7 +232,7 @@ describe('index', () => {
});

test('Hasher not found', async () => {
const sdjwt = new SDJwtInstance({});
const sdjwt = new TestInstance({});
try {
const credential = await sdjwt.issue(
{
Expand All @@ -244,7 +253,7 @@ describe('index', () => {
});

test('SaltGenerator not found', async () => {
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
hasher: digest,
});
try {
Expand All @@ -267,7 +276,7 @@ describe('index', () => {
});

test('Signer not found', async () => {
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
hasher: digest,
saltGenerator: generateSalt,
});
Expand All @@ -292,7 +301,7 @@ describe('index', () => {

test('Verifier not found', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
hasher: digest,
saltGenerator: generateSalt,
Expand Down Expand Up @@ -333,7 +342,7 @@ describe('index', () => {

test('kbSigner not found', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
verifier,
hasher: digest,
Expand Down Expand Up @@ -372,7 +381,7 @@ describe('index', () => {

test('kbVerifier not found', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
verifier,
hasher: digest,
Expand Down Expand Up @@ -413,7 +422,7 @@ describe('index', () => {

test('kbSignAlg not found', async () => {
const { signer, verifier } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
const sdjwt = new TestInstance({
signer,
verifier,
hasher: digest,
Expand Down Expand Up @@ -450,30 +459,7 @@ describe('index', () => {
});

test('hasher is not found', () => {
const sdjwt = new SDJwtInstance({});
const sdjwt = new TestInstance({});
expect(sdjwt.keys('')).rejects.toThrow('Hasher not found');
});

test('try to disclose a procted field', async () => {
const { signer } = createSignerVerifier();
const sdjwt = new SDJwtInstance({
signer,
hasher: digest,
saltGenerator: generateSalt,
signAlg: 'EdDSA',
});

const credential = sdjwt.issue(
{
foo: 'bar',
iss: 'Issuer',
iat: new Date().getTime(),
vct: '',
},
{
_sd: ['foo', 'iss'],
},
);
expect(credential).rejects.toThrow('Cannot disclose protected field');
});
});
Loading

0 comments on commit ed99188

Please sign in to comment.