UNPKG

@0xpolygonid/js-sdk

Version:
226 lines (190 loc) 7.21 kB
import { BasicMessage, IPacker, JWSPackerParams } from '../types'; import { AcceptJwsAlgorithms, MediaType, ProtocolVersion, SUPPORTED_PUBLIC_KEY_TYPES } from '../constants'; import { extractPublicKeyBytes, resolveVerificationMethods } from '../utils/did'; import { keyPath, KMS } from '../../kms/'; import { verifyJWS } from 'did-jwt'; import { DIDDocument, Resolvable, parse } from 'did-resolver'; import { bytesToBase64url } from '../../utils/encoding'; import { byteDecoder, byteEncoder, bytesToHex, decodeBase64url, encodeBase64url } from '../../utils'; import { parseAcceptProfile } from '../utils'; /** * Packer that can pack message to JWZ token, * and unpack and validate JWZ envelope * @public * @class ZKPPacker * @implements implements IPacker interface */ export class JWSPacker implements IPacker { private readonly supportedAlgorithms = [AcceptJwsAlgorithms.ES256K, AcceptJwsAlgorithms.ES256KR]; private readonly supportedProtocolVersions = [ProtocolVersion.V1]; /** * Creates an instance of JWSPacker. * * @param {KMS} _kms * @param {Resolvable} _documentResolver * @memberof JWSPacker */ constructor(private readonly _kms: KMS, private readonly _documentResolver: Resolvable) {} /** * Packs the given payload and returns a promise that resolves to the packed data. * * @param {Uint8Array} payload - The payload to be packed. * @param {PackerParams} param - The packing parameters. * @returns `Promise<Uint8Array>` */ packMessage(msg: BasicMessage, param: JWSPackerParams): Promise<Uint8Array> { return this.packInternal(msg, param); } /** * creates JSON Web Signature token * * @param {Uint8Array} payload - serialized message * @param {PackerParams} params - sender id and proving alg are required * @returns `Promise<Uint8Array>` */ async pack(payload: Uint8Array, params: JWSPackerParams): Promise<Uint8Array> { const message = JSON.parse(byteDecoder.decode(payload)); return this.packInternal(message, params); } /** * validate envelope which is jwz token * * @param {Uint8Array} envelope * @returns `Promise<BasicMessage>` */ async unpack(envelope: Uint8Array): Promise<BasicMessage> { const jws = byteDecoder.decode(envelope); const [headerStr, msgStr] = jws.split('.'); const header = JSON.parse(decodeBase64url(headerStr)); const message = JSON.parse(decodeBase64url(msgStr)); const explicitSender = parse(header.kid)?.did; if (explicitSender && explicitSender !== message.from) { throw new Error(`Sender does not match DID in message with kid ${header?.kid}`); } const didDocument: DIDDocument = await this.resolveDidDoc(message.from); let vms = resolveVerificationMethods(didDocument); if (!vms?.length) { throw new Error(`No verification methods defined in the DID document of ${didDocument.id}`); } if (header.kid) { const vm = vms.find((v) => { return v.id === header.kid; }); if (!vm) { throw new Error( `verification method with specified kid ${header.kid} is not found in the DID Document` ); } vms = [vm]; } const verificationResponse = verifyJWS(jws, vms); if (!verificationResponse) { throw new Error('JWS verification failed'); } return message as BasicMessage; } mediaType(): MediaType { return MediaType.SignedMessage; } /** {@inheritDoc IPacker.getSupportedProfiles} */ getSupportedProfiles(): string[] { return this.supportedProtocolVersions.map( (v) => `${v};env=${this.mediaType()};alg=${this.supportedAlgorithms.join(',')}` ); } /** {@inheritDoc IPacker.isProfileSupported} */ isProfileSupported(profile: string) { const { protocolVersion, env, circuits, alg } = parseAcceptProfile(profile); if (!this.supportedProtocolVersions.includes(protocolVersion)) { return false; } if (env !== this.mediaType()) { return false; } if (circuits) { throw new Error(`Circuits are not supported for ${env} media type`); } const algSupported = !alg?.length || alg.some((a) => this.supportedAlgorithms.includes(a as AcceptJwsAlgorithms)); return algSupported; } private async resolveDidDoc(from: string) { let didDocument: DIDDocument; try { const didResolutionResult = await this._documentResolver.resolve(from); if (!didResolutionResult?.didDocument?.id) { throw new Error(`did document for ${from} is not found in resolution result`); } didDocument = didResolutionResult.didDocument; } catch (err: unknown) { throw new Error(`did document for ${from} is not resolved: ${(err as Error).message}`); } return didDocument; } private async packInternal(message: BasicMessage, params: JWSPackerParams): Promise<Uint8Array> { if (!params.alg) { throw new Error('Missing algorithm'); } const from = message.from ?? ''; if (!from) { throw new Error('Missing sender DID'); } const vmTypes: string[] = SUPPORTED_PUBLIC_KEY_TYPES[params.alg as keyof typeof SUPPORTED_PUBLIC_KEY_TYPES]; if (!vmTypes?.length) { throw new Error(`No supported verification methods for algorithm ${params.alg}`); } const didDocument: DIDDocument = params.didDocument ?? (await this.resolveDidDoc(from)); const vms = resolveVerificationMethods(didDocument); if (!vms.length) { throw new Error(`No verification methods defined in the DID document of ${didDocument.id}`); } // try to find a managed signing key that matches keyRef const vm = params.kid ? vms.find((vm) => vm.id === params.kid) : vms[0]; if (!vm) { throw new Error(`No key found with id ${params.kid} in DID document of ${didDocument.id}`); } const { publicKeyBytes, kmsKeyType } = extractPublicKeyBytes(vm); if (!publicKeyBytes && !kmsKeyType) { if ((vm.blockchainAccountId || vm.ethereumAddress) && !params.signer) { throw new Error(`No signer provided for ${vm.blockchainAccountId || vm.ethereumAddress}`); } } const kid = vm.id; const headerObj = { alg: params.alg, kid, typ: MediaType.SignedMessage }; const header = encodeBase64url(JSON.stringify(headerObj)); const msg = encodeBase64url(JSON.stringify(message)); const signingInput = `${header}.${msg}`; const signingInputBytes = byteEncoder.encode(signingInput); let signatureBase64: string; if (params.signer) { const signature = await params.signer(vm, signingInputBytes); signatureBase64 = bytesToBase64url(signature); } else { if (!publicKeyBytes) { throw new Error('No public key found'); } if (!kmsKeyType) { throw new Error('No KMS key type found'); } const signatureBytes = await this._kms.sign( { type: kmsKeyType, id: keyPath(kmsKeyType, bytesToHex(publicKeyBytes)) }, signingInputBytes, { alg: params.alg } ); signatureBase64 = bytesToBase64url(signatureBytes); } return byteEncoder.encode(`${signingInput}.${signatureBase64}`); } }