UNPKG

@sd-jwt/core

Version:

sd-jwt draft 7 implementation in typescript

660 lines (644 loc) 25.4 kB
export { base64UrlToUint8Array, base64urlDecode, base64urlEncode, uint8ArrayToBase64Url } from '@owf/identity-common'; declare const SD_SEPARATOR = "~"; declare const SD_LIST_KEY = "..."; declare const SD_DIGEST = "_sd"; declare const SD_DECOY = "_sd_decoy"; declare const KB_JWT_TYP = "kb+jwt"; type SDJWTCompact = string; type Base64urlString = string; type DisclosureData<T> = [string, string, T] | [string, T]; declare const IANA_HASH_ALGORITHMS: readonly ["sha-256", "sha-256-128", "sha-256-120", "sha-256-96", "sha-256-64", "sha-256-32", "sha-384", "sha-512", "sha3-224", "sha3-256", "sha3-384", "sha3-512", "blake2s-256", "blake2b-256", "blake2b-512", "k12-256", "k12-512"]; type HashAlgorithm = (typeof IANA_HASH_ALGORITHMS)[number]; type SDJWTConfig<T = unknown> = { omitTyp?: boolean; hasher?: Hasher; hashAlg?: HashAlgorithm; saltGenerator?: SaltGenerator; signer?: Signer; signAlg?: string; verifier?: Verifier<T>; kbSigner?: Signer; kbSignAlg?: string; kbVerifier?: KbVerifier; }; type kbHeader = { typ: 'kb+jwt'; alg: string; }; type kbPayload = { iat: number; aud: string; nonce: string; sd_hash: string; }; type KBOptions = { payload: Omit<kbPayload, 'sd_hash'>; }; interface RsaOtherPrimesInfo { d?: string; r?: string; t?: string; } interface JsonWebKey { alg?: string; crv?: string; d?: string; dp?: string; dq?: string; e?: string; ext?: boolean; k?: string; key_ops?: string[]; kty?: string; n?: string; oth?: RsaOtherPrimesInfo[]; p?: string; q?: string; qi?: string; use?: string; x?: string; y?: string; } interface JwtPayload { cnf?: { jwk: JsonWebKey; }; exp?: number; [key: string]: unknown; } type OrPromise<T> = T | Promise<T>; type Signer = (data: string) => OrPromise<string>; type Verifier<T = unknown> = (data: string, sig: string, options?: T) => OrPromise<boolean>; type KbVerifier = (data: string, sig: string, payload: JwtPayload) => OrPromise<boolean>; type Hasher = (data: string | ArrayBuffer, alg: string) => OrPromise<Uint8Array>; type SaltGenerator = (length: number) => OrPromise<string>; type HasherAndAlg = { hasher: Hasher; alg: string; }; type SignerSync = (data: string) => string; type VerifierSync = (data: string, sig: string) => boolean; type HasherSync = (data: string, alg: string) => Uint8Array; type SaltGeneratorSync = (length: number) => string; type HasherAndAlgSync = { hasher: HasherSync; alg: string; }; type NonNever<T> = { [P in keyof T as T[P] extends never ? never : P]: T[P]; }; type SD<Payload> = { [SD_DIGEST]?: Array<keyof Payload>; }; type DECOY = { [SD_DECOY]?: number; }; /** * This is a disclosureFrame type that is used to represent the structure of what is being disclosed. * DisclosureFrame is made from the payload type. * * For example, if the payload is * { * foo: 'bar', * test: { * zzz: 'yyy', * } * arr: ['1', '2', {a: 'b'}] * } * * The disclosureFrame can be subset of: * { * _sd: ["foo", "test", "arr"], * test: { * _sd: ["zzz"], * }, * arr: { * _sd: ["0", "1", "2"], * "2": { * _sd: ["a"], * } * } * } * * The disclosureFrame can be used with decoy. * Decoy can be used like this: * { * ... * _sd: ... * _sd_decoy: 1 // number of decoy in this layer * } * */ type Frame<Payload> = Payload extends Array<infer U> ? U extends object ? Record<number, Frame<U>> & SD<Payload> & DECOY : SD<Payload> & DECOY : Payload extends Record<string, unknown> ? NonNever<{ [K in keyof Payload]?: NonNullable<Payload[K]> extends object ? Frame<Payload[K]> : never; } & SD<Payload> & DECOY> : SD<Payload> & DECOY; /** * This is a disclosureFrame type that is used to represent the structure of what is being disclosed. */ type Extensible = Record<string, unknown | boolean>; type DisclosureFrame<T extends Extensible> = Frame<T>; /** * This is a presentationFrame type that is used to represent the structure of what is being presented. * PresentationFrame is made from the payload type. * const claims = { firstname: 'John', lastname: 'Doe', ssn: '123-45-6789', id: '1234', data: { firstname: 'John', lastname: 'Doe', ssn: '123-45-6789', list: [{ r: 'd' }, 'b', 'c'], list2: ['1', '2', '3'], list3: ['1', null, 2], }, data2: { hi: 'bye', }, }; Example of a presentationFrame: const presentationFrame: PresentationFrame<typeof claims> = { firstname: true, lastname: true, ssn: true, id: 'true', data: { firstname: true, list: { 1: true, 0: { r: true, }, }, list2: { 1: true, }, list3: true, }, data2: true, }; */ type PFrame<Payload> = Payload extends Array<infer U> ? U extends object ? Record<number, PFrame<U> | boolean> | boolean : Record<number, boolean> | boolean : { [K in keyof Payload]?: NonNullable<Payload[K]> extends object ? PFrame<Payload[K]> | boolean : boolean; }; type PresentationFrame<T extends Extensible> = PFrame<T>; /** * Error codes for SD-JWT verification errors. */ type VerificationErrorCode = 'HASHER_NOT_FOUND' | 'VERIFIER_NOT_FOUND' | 'INVALID_SD_JWT' | 'INVALID_JWT_FORMAT' | 'JWT_NOT_YET_VALID' | 'JWT_EXPIRED' | 'INVALID_JWT_SIGNATURE' | 'MISSING_REQUIRED_CLAIMS' | 'KEY_BINDING_JWT_MISSING' | 'KEY_BINDING_VERIFIER_NOT_FOUND' | 'KEY_BINDING_SIGNATURE_INVALID' | 'KEY_BINDING_SD_HASH_INVALID' | 'STATUS_VERIFICATION_FAILED' | 'STATUS_INVALID' | 'VCT_VERIFICATION_FAILED' | 'UNKNOWN_ERROR'; /** * Represents a single verification error. */ type VerificationError = { /** * The error code identifying the type of error. */ code: VerificationErrorCode; /** * Human-readable error message. */ message: string; /** * Optional additional details about the error. */ details?: unknown; }; /** * Result type for safe verification that collects all errors. */ type SafeVerifyResult<T> = { success: true; data: T; errors?: never; } | { success: false; data?: never; errors: VerificationError[]; }; type FlattenJSONData = { jwtData: { protected: string; payload: string; signature: string; }; disclosures: Array<string>; kb_jwt?: string; }; type FlattenJSONSerialized = { payload: string; signature: string; protected: string; header: { disclosures: Array<string>; kb_jwt?: string; }; }; declare class FlattenJSON { disclosures: Array<string>; kb_jwt?: string; payload: string; signature: string; protected: string; constructor(data: FlattenJSONData); static fromEncode(encodedSdJwt: string): FlattenJSON; static fromSerialized(json: FlattenJSONSerialized): FlattenJSON; toJson(): FlattenJSONSerialized; toEncoded(): string; } type GeneralJSONData = { payload: string; disclosures: Array<string>; kb_jwt?: string; signatures: Array<{ protected: string; signature: string; kid?: string; }>; }; type GeneralJSONSerialized = { payload: string; signatures: Array<{ header: { disclosures?: Array<string>; kid?: string; kb_jwt?: string; }; protected: string; signature: string; }>; }; declare class GeneralJSON { payload: string; disclosures: Array<string>; kb_jwt?: string; signatures: Array<{ protected: string; signature: string; kid?: string; }>; constructor(data: GeneralJSONData); static fromEncode(encodedSdJwt: string): GeneralJSON; static fromSerialized(json: GeneralJSONSerialized): GeneralJSON; toJson(): { payload: string; signatures: ({ header: { kid: string | undefined; disclosures?: undefined; kb_jwt?: undefined; }; protected: string; signature: string; } | { header: { disclosures: string[]; kid: string | undefined; kb_jwt: string | undefined; }; protected: string; signature: string; })[]; }; toEncoded(index: number): string; addSignature(protectedHeader: Record<string, unknown>, signer: Signer, kid?: string): Promise<void>; } type JwtData<Header extends Record<string, unknown>, Payload extends Record<string, unknown>> = { header?: Header; payload?: Payload; signature?: Base64urlString; encoded?: string; }; /** * Options for the JWT verifier */ type VerifierOptions = { /** * current time in seconds since epoch */ currentDate?: number; /** * allowed skew for the current time in seconds. Positive value that will lower the iat and nbf checks, and increase the exp check. */ skewSeconds?: number; /** * required claim keys for the payload. * If the payload does not contain these keys, the verification will fail. */ requiredClaimKeys?: string[]; /** * nonce used to verify the key binding jwt to prevent replay attacks. */ keyBindingNonce?: string; /** * disable the verification of the status claim in the payload. * * @default false */ disableStatusVerification?: boolean; /** * any other custom options */ [key: string]: unknown; }; declare class Jwt<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>> { header?: Header; payload?: Payload; signature?: Base64urlString; private encoded?; constructor(data?: JwtData<Header, Payload>); static decodeJWT<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>>(jwt: string): { header: Header; payload: Payload; signature: Base64urlString; }; static fromEncode<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>>(encodedJwt: string): Jwt<Header, Payload>; setHeader(header: Header): Jwt<Header, Payload>; setPayload(payload: Payload): Jwt<Header, Payload>; protected getUnsignedToken(): string; sign(signer: Signer): Promise<string>; encodeJwt(): string; /** * Verify the JWT using the provided verifier function. * It checks the signature and validates the iat, nbf, and exp claims if they are present. * @param verifier * @param options - Options for verification, such as current date and skew seconds * @returns */ verify<T>(verifier: Verifier<T>, options?: T & VerifierOptions): Promise<{ payload: Payload | undefined; header: Header | undefined; }>; } declare class KBJwt<Header extends kbHeader = kbHeader, Payload extends kbPayload = kbPayload> extends Jwt<Header, Payload> { verifyKB(values: { verifier: KbVerifier; payload: Record<string, unknown>; nonce: string; /** * Options forwarded to the common JWT verification, e.g. currentDate and * skewSeconds used to validate the iat, nbf and exp claims. */ options?: VerifierOptions; }): Promise<{ payload: Payload; header: Header; }>; static fromKBEncode<Header extends kbHeader = kbHeader, Payload extends kbPayload = kbPayload>(encodedJwt: string): KBJwt<Header, Payload>; } declare class Disclosure<T = unknown> { salt: string; key?: string; value: T; _digest: string | undefined; private _encoded; constructor(data: DisclosureData<T>, _meta?: { digest: string; encoded: string; }); static fromEncode<T>(s: string, hash: HasherAndAlg): Promise<Disclosure<T>>; static fromEncodeSync<T>(s: string, hash: HasherAndAlgSync): Disclosure<T>; static fromArray<T>(item: DisclosureData<T>, _meta?: { digest: string; encoded: string; }): Disclosure<T>; encode(): string; decode(): DisclosureData<T>; digest(hash: HasherAndAlg): Promise<string>; digestSync(hash: HasherAndAlgSync): string; } declare class SDJWTException extends Error { details?: unknown; constructor(message: string, details?: unknown); getFullMessage(): string; } /** * Narrows an unknown caught value to an Error instance. */ declare function ensureError(value: unknown): Error; type SDJwtData<Header extends Record<string, unknown>, Payload extends Record<string, unknown>, KBHeader extends kbHeader = kbHeader, KBPayload extends kbPayload = kbPayload> = { jwt?: Jwt<Header, Payload>; disclosures?: Array<Disclosure>; kbJwt?: KBJwt<KBHeader, KBPayload>; }; declare class SDJwt<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>, KBHeader extends kbHeader = kbHeader, KBPayload extends kbPayload = kbPayload> { jwt?: Jwt<Header, Payload>; disclosures?: Array<Disclosure>; kbJwt?: KBJwt<KBHeader, KBPayload>; constructor(data?: SDJwtData<Header, Payload, KBHeader, KBPayload>); static decodeSDJwt<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>, KBHeader extends kbHeader = kbHeader, KBPayload extends kbPayload = kbPayload>(sdjwt: SDJWTCompact, hasher: Hasher): Promise<{ jwt: Jwt<Header, Payload>; disclosures: Array<Disclosure>; kbJwt?: KBJwt<KBHeader, KBPayload>; }>; static extractJwt<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>>(encodedSdJwt: SDJWTCompact): Promise<Jwt<Header, Payload>>; static fromEncode<Header extends Record<string, unknown> = Record<string, unknown>, Payload extends Record<string, unknown> = Record<string, unknown>, KBHeader extends kbHeader = kbHeader, KBPayload extends kbPayload = kbPayload>(encodedSdJwt: SDJWTCompact, hasher: Hasher): Promise<SDJwt<Header, Payload>>; present<T extends Record<string, unknown>>(presentFrame: PresentationFrame<T> | undefined, hasher: Hasher): Promise<SDJWTCompact>; getPresentDisclosures<T extends Record<string, unknown>>(presentFrame: PresentationFrame<T> | undefined, hasher: Hasher): Promise<Disclosure<unknown>[]>; encodeSDJwt(): SDJWTCompact; keys(hasher: Hasher): Promise<string[]>; presentableKeys(hasher: Hasher): Promise<string[]>; getClaims<T>(hasher: Hasher): Promise<T>; } declare const listKeys: (obj: Record<string, unknown>, prefix?: string) => string[]; declare const pack: <T extends Record<string, unknown>>(claims: T, disclosureFrame: DisclosureFrame<T> | undefined, hash: HasherAndAlg, saltGenerator: SaltGenerator) => Promise<{ packedClaims: Record<string, unknown> | Array<Record<string, unknown>>; disclosures: Array<Disclosure>; }>; declare const decodeJwt: <H extends Record<string, unknown>, T extends Record<string, unknown>>(jwt: string) => { header: H; payload: T; signature: string; }; declare const splitSdJwt: (sdjwt: string) => { jwt: string; disclosures: string[]; kbJwt?: string; }; declare const decodeSdJwt: (sdjwt: string, hasher: Hasher) => Promise<DecodedSDJwt>; declare const decodeSdJwtSync: (sdjwt: string, hasher: HasherSync) => DecodedSDJwt; declare const getClaims: <T = Record<string, unknown>>(rawPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: Hasher) => Promise<T>; declare const getClaimsSync: <T = Record<string, unknown>>(rawPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: HasherSync) => T; declare const unpackObj: (obj: unknown, map: Record<string, Disclosure>) => { unpackedObj: unknown; disclosureKeymap: Record<string, string>; }; declare const createHashMapping: (disclosures: Array<Disclosure>, hash: HasherAndAlg) => Promise<Record<string, Disclosure<unknown>>>; declare const createHashMappingSync: (disclosures: Array<Disclosure>, hash: HasherAndAlgSync) => Record<string, Disclosure<unknown>>; declare const getSDAlgAndPayload: (SdJwtPayload: Record<string, unknown>) => { _sd_alg: string; payload: { [x: string]: unknown; }; }; declare const unpack: (SdJwtPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: Hasher) => Promise<{ unpackedObj: unknown; disclosureKeymap: Record<string, string>; }>; declare const unpackSync: (SdJwtPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: HasherSync) => { unpackedObj: unknown; disclosureKeymap: Record<string, string>; }; type DecodedSDJwt = { jwt: { header: Record<string, unknown>; payload: Record<string, unknown>; signature: string; }; disclosures: Array<Disclosure>; kbJwt?: { header: Record<string, unknown>; payload: Record<string, unknown>; signature: string; }; }; declare const createDecoy: (hash: HasherAndAlg, saltGenerator: SaltGenerator) => Promise<string>; declare const presentableKeys: (rawPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: Hasher) => Promise<string[]>; declare const presentableKeysSync: (rawPayload: Record<string, unknown>, disclosures: Array<Disclosure>, hasher: HasherSync) => string[]; declare const present: <T extends Record<string, unknown>>(sdJwt: string, presentFrame: PresentationFrame<T>, hasher: Hasher) => Promise<string>; declare const presentSync: <T extends Record<string, unknown>>(sdJwt: string, presentFrame: PresentationFrame<T>, hasher: HasherSync) => string; /** * Transform the object keys into an array of strings. We are not sorting the array in any way. * @param obj The object to transform * @param prefix The prefix to add to the keys * @returns */ declare const transformPresentationFrame: (obj: PresentationFrame<Extensible>, prefix?: string) => string[]; type SerializedDisclosure = { digest: string; encoded: string; salt: string; key: string | undefined; value: unknown; }; declare const createHashMappingForSerializedDisclosure: (disclosures: SerializedDisclosure[]) => Record<string, Disclosure<unknown>>; /** * This function selects the serialized disclosures from the payload * and array of serialized disclosure based on the presentation frame. * If you want to know what is serialized disclosures, check type SerializedDisclosure. * @param payload: Record<string, unknown> * @param disclosures: SerializedDisclosure[] * @param presentationFrame: PresentationFrame<T> */ declare const selectDisclosures: <T extends Record<string, unknown>>(payload: Record<string, unknown>, disclosures: SerializedDisclosure[], presentationFrame: PresentationFrame<T>) => SerializedDisclosure[]; type SdJwtPayload = Record<string, unknown>; declare class SDJwtInstance<ExtendedPayload extends SdJwtPayload, T = unknown> { protected type?: string; static readonly DEFAULT_hashAlg = "sha-256"; protected userConfig: SDJWTConfig<T>; constructor(userConfig?: SDJWTConfig<T>); private createKBJwt; private SignJwt; private VerifyJwt; issue<Payload extends ExtendedPayload>(payload: Payload, disclosureFrame?: DisclosureFrame<Payload>, options?: { header?: object; }): Promise<SDJWTCompact>; /** * Validates if the payload contains any reserved claim names. If so it will throw an error. * @param payload * @returns */ protected validateReservedFields<T extends ExtendedPayload>(payload: T): void; protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void; present<T extends Record<string, unknown>>(encodedSDJwt: string, presentationFrame?: PresentationFrame<T>, options?: { kb?: KBOptions; }): Promise<SDJWTCompact>; verify(encodedSDJwt: string, options?: T & VerifierOptions): Promise<{ payload: ExtendedPayload; header: Record<string, unknown> | undefined; kb?: undefined; } | { payload: ExtendedPayload; header: Record<string, unknown> | undefined; kb: { payload: kbPayload; header: kbHeader; }; }>; /** * Safe verification that collects all errors instead of failing fast. * Returns a result object with either the verified data or an array of all errors. * * @param encodedSDJwt - The encoded SD-JWT to verify * @param options - Verification options * @returns A SafeVerifyResult containing either success data or collected errors */ safeVerify(encodedSDJwt: string, options?: T & VerifierOptions): Promise<SafeVerifyResult<{ payload: ExtendedPayload; header: Record<string, unknown> | undefined; kb?: { payload: Record<string, unknown>; header: Record<string, unknown>; }; }>>; private calculateSDHash; /** * This function is for validating the SD JWT * Checking signature, if provided the iat and exp when provided and return its the claims * @param encodedSDJwt * @param options * @returns */ validate(encodedSDJwt: string, options?: T & VerifierOptions): Promise<{ payload: ExtendedPayload; header: Record<string, unknown> | undefined; }>; config(newConfig: SDJWTConfig): void; encode(sdJwt: SDJwt): SDJWTCompact; decode(endcodedSDJwt: SDJWTCompact): Promise<SDJwt<Record<string, unknown>, Record<string, unknown>, kbHeader, kbPayload>>; keys(endcodedSDJwt: SDJWTCompact): Promise<string[]>; presentableKeys(endcodedSDJwt: SDJWTCompact): Promise<string[]>; getClaims(endcodedSDJwt: SDJWTCompact): Promise<unknown>; toFlattenJSON(endcodedSDJwt: SDJWTCompact): FlattenJSON; toGeneralJSON(endcodedSDJwt: SDJWTCompact): GeneralJSON; } declare class SDJwtGeneralJSONInstance<ExtendedPayload extends SdJwtPayload> { protected type?: string; static readonly DEFAULT_hashAlg = "sha-256"; protected userConfig: SDJWTConfig; constructor(userConfig?: SDJWTConfig); private createKBJwt; private encodeObj; issue<Payload extends ExtendedPayload>(payload: Payload, disclosureFrame: DisclosureFrame<Payload> | undefined, options: { sigs: Array<{ signer: Signer; alg: string; kid: string; header?: Record<string, unknown>; }>; }): Promise<GeneralJSON>; /** * Validates if the payload contains any reserved claim names. If so it will throw an error. * @param payload * @returns */ protected validateReservedFields<T extends ExtendedPayload>(payload: T): void; protected validateDisclosureFrame<T extends ExtendedPayload>(_disclosureFrame?: DisclosureFrame<T>): void; present<T extends Record<string, unknown>>(generalJSON: GeneralJSON, presentationFrame?: PresentationFrame<T>, options?: { kb?: KBOptions; }): Promise<GeneralJSON>; verify(generalJSON: GeneralJSON, options?: VerifierOptions): Promise<{ payload: ExtendedPayload; headers: unknown[]; kb?: undefined; } | { payload: ExtendedPayload; headers: unknown[]; kb: { payload: kbPayload; header: kbHeader; }; }>; private calculateSDHash; validate(generalJSON: GeneralJSON): Promise<{ payload: ExtendedPayload; headers: unknown[]; }>; config(newConfig: SDJWTConfig): void; encode(sdJwt: GeneralJSON, index: number): SDJWTCompact; decode(endcodedSDJwt: SDJWTCompact): GeneralJSON; keys(generalSdjwt: GeneralJSON): Promise<string[]>; presentableKeys(generalSdjwt: GeneralJSON): Promise<string[]>; getClaims(generalSdjwt: GeneralJSON): Promise<unknown>; } export { type Base64urlString, type DECOY, type DecodedSDJwt, Disclosure, type DisclosureData, type DisclosureFrame, type Extensible, FlattenJSON, type FlattenJSONData, type FlattenJSONSerialized, GeneralJSON, type GeneralJSONData, type GeneralJSONSerialized, type HashAlgorithm, type Hasher, type HasherAndAlg, type HasherAndAlgSync, type HasherSync, IANA_HASH_ALGORITHMS, Jwt, type JwtData, type JwtPayload, KBJwt, type KBOptions, KB_JWT_TYP, type KbVerifier, type OrPromise, type PresentationFrame, type SD, type SDJWTCompact, type SDJWTConfig, SDJWTException, SDJwt, type SDJwtData, SDJwtGeneralJSONInstance, SDJwtInstance, SD_DECOY, SD_DIGEST, SD_LIST_KEY, SD_SEPARATOR, type SafeVerifyResult, type SaltGenerator, type SaltGeneratorSync, type SdJwtPayload, type SerializedDisclosure, type Signer, type SignerSync, type VerificationError, type VerificationErrorCode, type Verifier, type VerifierOptions, type VerifierSync, createDecoy, createHashMapping, createHashMappingForSerializedDisclosure, createHashMappingSync, decodeJwt, decodeSdJwt, decodeSdJwtSync, ensureError, getClaims, getClaimsSync, getSDAlgAndPayload, type kbHeader, type kbPayload, listKeys, pack, present, presentSync, presentableKeys, presentableKeysSync, selectDisclosures, splitSdJwt, transformPresentationFrame, unpack, unpackObj, unpackSync };