UNPKG

@sd-jwt/core

Version:

sd-jwt draft 7 implementation in typescript

149 lines (134 loc) 3.76 kB
import { splitSdJwt } from '@sd-jwt/decode'; import { SD_SEPARATOR, type Signer } from '@sd-jwt/types'; import { base64urlEncode, SDJWTException } from '@sd-jwt/utils'; export type GeneralJSONData = { payload: string; disclosures: Array<string>; kb_jwt?: string; signatures: Array<{ protected: string; signature: string; kid?: string; }>; }; export type GeneralJSONSerialized = { payload: string; signatures: Array<{ header: { disclosures?: Array<string>; kid?: string; kb_jwt?: string; }; protected: string; signature: string; }>; }; export class GeneralJSON { public payload: string; public disclosures: Array<string>; public kb_jwt?: string; public signatures: Array<{ protected: string; signature: string; kid?: string; }>; constructor(data: GeneralJSONData) { this.payload = data.payload; this.disclosures = data.disclosures; this.kb_jwt = data.kb_jwt; this.signatures = data.signatures; } public static fromEncode(encodedSdJwt: string) { const { jwt, disclosures, kbJwt } = splitSdJwt(encodedSdJwt); const { 0: protectedHeader, 1: payload, 2: signature } = jwt.split('.'); if (!protectedHeader || !payload || !signature) { throw new SDJWTException('Invalid JWT'); } return new GeneralJSON({ payload, disclosures, kb_jwt: kbJwt, signatures: [ { protected: protectedHeader, signature, }, ], }); } public static fromSerialized(json: GeneralJSONSerialized) { if (!json.signatures[0]) { throw new SDJWTException('Invalid JSON'); } const disclosures = json.signatures[0].header?.disclosures ?? []; const kb_jwt = json.signatures[0].header?.kb_jwt; return new GeneralJSON({ payload: json.payload, disclosures, kb_jwt, signatures: json.signatures.map((s) => { return { protected: s.protected, signature: s.signature, kid: s.header?.kid, }; }), }); } public toJson() { return { payload: this.payload, signatures: this.signatures.map((s, i) => { if (i !== 0) { // If present, disclosures and kb_jwt, MUST be included in the first unprotected header and // MUST NOT be present in any following unprotected headers. return { header: { kid: s.kid, }, protected: s.protected, signature: s.signature, }; } return { header: { disclosures: this.disclosures, kid: s.kid, kb_jwt: this.kb_jwt, }, protected: s.protected, signature: s.signature, }; }), }; } public toEncoded(index: number) { if (index < 0 || index >= this.signatures.length) { throw new SDJWTException('Index out of bounds'); } const data: string[] = []; const { protected: protectedHeader, signature } = this.signatures[index]; const jwt = `${protectedHeader}.${this.payload}.${signature}`; data.push(jwt); if (this.disclosures && this.disclosures.length > 0) { const disclosures = this.disclosures.join(SD_SEPARATOR); data.push(disclosures); } const kb = this.kb_jwt ?? ''; data.push(kb); return data.join(SD_SEPARATOR); } public async addSignature( protectedHeader: Record<string, unknown>, signer: Signer, kid?: string, ) { const header = base64urlEncode(JSON.stringify(protectedHeader)); const signature = await signer(`${header}.${this.payload}`); this.signatures.push({ protected: header, signature, kid, }); } }