UNPKG

@sphereon/oid4vc-common

Version:

OpenID 4 Verifiable Credentials Common

131 lines (102 loc) 4.29 kB
import { JWK, JwtHeader, JwtPayload, SigningAlgo } from '..'; import { JwtProtectionMethod, JwtType } from './jwtUtils'; export interface JwtVerifierBase { type: JwtType; method: JwtProtectionMethod; } export interface DidJwtVerifier extends JwtVerifierBase { method: 'did'; alg: SigningAlgo | string; didUrl: string; } export interface X5cJwtVerifier extends JwtVerifierBase { method: 'x5c'; alg: SigningAlgo | string; /** * * Array of base64-encoded certificate strings in the DER-format. * * The certificate containing the public key corresponding to the key used to digitally sign the JWS MUST be the first certificate. */ x5c: Array<string>; /** * The jwt issuer */ issuer: string; } export interface OpenIdFederationJwtVerifier extends JwtVerifierBase { method: 'openid-federation'; /** * The OpenId federation Entity */ entityId: string; } export interface JwkJwtVerifier extends JwtVerifierBase { method: 'jwk'; alg: SigningAlgo | string; jwk: JWK; } export interface CustomJwtVerifier extends JwtVerifierBase { method: 'custom'; } export type JwtVerifier = DidJwtVerifier | X5cJwtVerifier | CustomJwtVerifier | JwkJwtVerifier | OpenIdFederationJwtVerifier; export const getDidJwtVerifier = (jwt: { header: JwtHeader; payload: JwtPayload }, options: { type: JwtType }): DidJwtVerifier => { const { type } = options; if (!jwt.header.kid) throw new Error(`Received an invalid JWT. Missing kid header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (!jwt.header.kid.includes('#')) { throw new Error(`Received an invalid JWT.. '${type}' contains an invalid kid header.`); } return { method: 'did', didUrl: jwt.header.kid, type: type, alg: jwt.header.alg }; }; const getIssuer = (type: JwtType, payload: JwtPayload): string => { // For 'request-object' the `iss` value is not required so we map the issuer to client_id if (type === 'request-object') { if (!payload.client_id) { throw new Error('Missing required field client_id in request object JWT'); } return payload.client_id as string; } if (typeof payload.iss !== 'string') { throw new Error(`Received an invalid JWT. '${type}' contains an invalid iss claim or it is missing.`); } return payload.iss; }; export const getX5cVerifier = (jwt: { header: JwtHeader; payload: JwtPayload }, options: { type: JwtType }): X5cJwtVerifier => { const { type } = options; if (!jwt.header.x5c) throw new Error(`Received an invalid JWT. Missing x5c header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (!Array.isArray(jwt.header.x5c) || jwt.header.x5c.length === 0 || !jwt.header.x5c.every((cert) => typeof cert === 'string')) { throw new Error(`Received an invalid JWT.. '${type}' contains an invalid x5c header.`); } return { method: 'x5c', x5c: jwt.header.x5c, issuer: getIssuer(type, jwt.payload), type: type, alg: jwt.header.alg, }; }; export const getJwkVerifier = async (jwt: { header: JwtHeader; payload: JwtPayload }, options: { type: JwtType }): Promise<JwkJwtVerifier> => { const { type } = options; if (!jwt.header.jwk) throw new Error(`Received an invalid JWT. Missing jwk header.`); if (!jwt.header.alg) throw new Error(`Received an invalid JWT. Missing alg header.`); if (typeof jwt.header.jwk !== 'object') { throw new Error(`Received an invalid JWT. '${type}' contains an invalid jwk header.`); } return { method: 'jwk', type, jwk: jwt.header.jwk, alg: jwt.header.alg }; }; export const getJwtVerifierWithContext = async ( jwt: { header: JwtHeader; payload: JwtPayload }, options: { type: JwtType }, ): Promise<JwtVerifier> => { const { header, payload } = jwt; if (header.kid?.startsWith('did:')) return getDidJwtVerifier({ header, payload }, options); else if (jwt.header.x5c) return getX5cVerifier({ header, payload }, options); else if (jwt.header.jwk) return getJwkVerifier({ header, payload }, options); return { method: 'custom', type: options.type }; }; export type VerifyJwtCallbackBase<T extends JwtVerifier> = ( jwtVerifier: T, jwt: { header: JwtHeader; payload: JwtPayload; raw: string }, ) => Promise<boolean>;