@sd-jwt/core
Version:
sd-jwt draft 7 implementation in typescript
385 lines (331 loc) • 9.54 kB
text/typescript
import Crypto from 'node:crypto';
import { generateSalt, digest as hasher } from '@sd-jwt/crypto-nodejs';
import { createHashMapping, unpack } from '@sd-jwt/decode';
import type { DisclosureFrame, Signer } from '@sd-jwt/types';
import { Disclosure } from '@sd-jwt/utils';
import { describe, expect, test } from 'vitest';
import { Jwt } from '../jwt';
import { listKeys, pack, SDJwt } from '../sdjwt';
const hash = { alg: 'SHA256', hasher };
describe('SD JWT', () => {
test('create and encode', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
expect(sdJwt).toBeDefined();
const encoded = sdJwt.encodeSDJwt();
expect(encoded).toBeDefined();
});
test('decode', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar', _sd_alg: 'sha-256' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const encoded = sdJwt.encodeSDJwt();
const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
expect(newSdJwt).toBeDefined();
const newJwt = newSdJwt.jwt;
expect(newJwt?.header).toEqual(jwt.header);
expect(newJwt?.payload).toEqual(jwt.payload);
expect(newJwt?.signature).toEqual(jwt.signature);
});
test('decode compatibilty', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const encoded = sdJwt.encodeSDJwt();
const newSdJwt = await SDJwt.fromEncode(encoded, hasher);
expect(newSdJwt).toBeDefined();
const newJwt = newSdJwt.jwt;
expect(newJwt?.header).toEqual(jwt.header);
expect(newJwt?.payload).toEqual(jwt.payload);
expect(newJwt?.signature).toEqual(jwt.signature);
});
test('keys', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const keys = await sdJwt.keys(hasher);
expect(keys).toBeDefined();
expect(keys).toEqual(['foo']);
});
test('presentable keys', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const keys = await sdJwt.presentableKeys(hasher);
expect(keys).toBeDefined();
expect(keys).toEqual([]);
});
test('claims', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const claims = await sdJwt.getClaims(hasher);
expect(claims).toBeDefined();
expect(claims).toEqual({
foo: 'bar',
});
});
test('pack', async () => {
const claim = {
firstname: 'John',
lastname: 'Doe',
};
const { packedClaims, disclosures } = await pack(
claim,
{
_sd: ['firstname'],
},
hash,
generateSalt,
);
expect(disclosures).toBeDefined();
expect(packedClaims).toBeDefined();
expect(disclosures.length).toEqual(1);
expect(disclosures[0].key).toEqual('firstname');
expect(disclosures[0].value).toEqual('John');
expect(packedClaims._sd).toBeDefined();
expect(packedClaims._sd.length).toEqual(1);
expect(packedClaims.lastname).toEqual('Doe');
});
test('list keys', () => {
const data = {
a: {
b: {
c: 1,
},
d: [
{
e: 'fasfdsa',
f: 1234,
},
3,
4,
],
},
g: [['h'], 'i'],
};
const keys = listKeys(data);
expect(keys).toEqual([
'a',
'a.b',
'a.b.c',
'a.d',
'a.d.0',
'a.d.0.e',
'a.d.0.f',
'a.d.1',
'a.d.2',
'g',
'g.0',
'g.0.0',
'g.1',
]);
});
test('presentable keys', async () => {
const claim = {
firstname: 'John',
lastname: 'Doe',
};
const { packedClaims, disclosures } = await pack(
claim,
{
_sd: ['firstname'],
},
hash,
generateSalt,
);
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: packedClaims,
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures,
});
const keys = await sdJwt.presentableKeys(hasher);
expect(keys).toBeDefined();
expect(keys).toEqual(['firstname']);
});
test('hash map', async () => {
const claim = {
firstname: 'John',
lastname: 'Doe',
};
const { disclosures } = await pack(
claim,
{
_sd: ['firstname'],
},
hash,
generateSalt,
);
const mapping = await createHashMapping(disclosures, hash);
expect(mapping).toBeDefined();
expect(Object.keys(mapping).length).toEqual(1);
expect(mapping[Object.keys(mapping)[0]]).toBeInstanceOf(Disclosure);
});
test('unpack', async () => {
const claim = {
firstname: 'John',
lastname: 'Doe',
};
const { packedClaims, disclosures } = await pack(
claim,
{
_sd: ['firstname'],
},
hash,
generateSalt,
);
const { disclosureKeymap, unpackedObj } = await unpack(
packedClaims,
disclosures,
hasher,
);
expect(disclosureKeymap).toBeDefined();
expect(unpackedObj).toBeDefined();
expect(unpackedObj).toEqual({
firstname: 'John',
lastname: 'Doe',
});
expect(disclosureKeymap.firstname).toBeDefined();
expect(typeof disclosureKeymap.firstname).toEqual('string');
});
test('pack and unpack', async () => {
const claims = {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
id: '1234',
data: {
firstname: 'John',
lastname: 'Doe',
ssn: '123-45-6789',
list: [{ r: '1' }, 'b', 'c'],
},
data2: {
hi: 'bye',
},
};
const disclosureFrame: DisclosureFrame<typeof claims> = {
_sd: ['firstname', 'id', 'data2'],
data: {
_sd: ['list'],
_sd_decoy: 2,
list: {
_sd: [0, 2],
_sd_decoy: 1,
0: {
_sd: ['r'],
},
},
},
data2: {
_sd: ['hi'],
},
};
const { packedClaims, disclosures } = await pack(
claims,
disclosureFrame,
hash,
generateSalt,
);
const { unpackedObj } = await unpack(packedClaims, disclosures, hasher);
expect(unpackedObj).toEqual(claims);
});
test('no disclosures', async () => {
const { privateKey } = Crypto.generateKeyPairSync('ed25519');
const testSigner: Signer = async (data: string) => {
const sig = Crypto.sign(null, Buffer.from(data), privateKey);
return Buffer.from(sig).toString('base64url');
};
const jwt = new Jwt({
header: { alg: 'EdDSA' },
payload: { foo: 'bar' },
});
await jwt.sign(testSigner);
const sdJwt = new SDJwt({
jwt,
disclosures: [],
});
const credential = sdJwt.encodeSDJwt();
const decoded = await SDJwt.decodeSDJwt(credential, hasher);
expect(jwt.header).toEqual(decoded.jwt.header);
expect(jwt.payload).toEqual(decoded.jwt.payload);
expect(jwt.signature).toEqual(decoded.jwt.signature);
expect(decoded.disclosures).toEqual([]);
});
});