UNPKG

@iden3/js-iden3-auth

Version:

iden3-auth implementation in JavaScript

313 lines (312 loc) 14.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Verifier = exports.createAuthorizationRequestWithMessage = exports.createAuthorizationRequest = void 0; const authV2_1 = require("../circuits/authV2"); const uuid_1 = require("uuid"); const registry_1 = require("../circuits/registry"); const js_jwz_1 = require("@iden3/js-jwz"); const js_sdk_1 = require("@0xpolygonid/js-sdk"); const path_1 = __importDefault(require("path")); const js_iden3_core_1 = require("@iden3/js-iden3-core"); /** * createAuthorizationRequest is a function to create protocol authorization request * @param {string} reason - reason to request proof * @param {string} sender - sender did * @param {string} callbackUrl - callback that user should use to send response * @returns `Promise<AuthorizationRequestMessage>` */ function createAuthorizationRequest(reason, sender, callbackUrl, opts) { return createAuthorizationRequestWithMessage(reason, '', sender, callbackUrl, opts); } exports.createAuthorizationRequest = createAuthorizationRequest; /** * createAuthorizationRequestWithMessage is a function to create protocol authorization request with explicit message to sign * @param {string} reason - reason to request proof * @param {string} message - message to sign in the response * @param {string} sender - sender did * @param {string} callbackUrl - callback that user should use to send response * @returns `Promise<AuthorizationRequestMessage>` */ function createAuthorizationRequestWithMessage(reason, message, sender, callbackUrl, opts) { const uuid = (0, uuid_1.v4)(); const request = { id: uuid, thid: uuid, from: sender, typ: js_sdk_1.PROTOCOL_CONSTANTS.MediaType.PlainMessage, type: js_sdk_1.PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE, body: { accept: opts?.accept, reason: reason, message: message, callbackUrl: callbackUrl, scope: opts?.scope || [] }, created_time: (0, js_iden3_core_1.getUnixTimestamp)(new Date()), expires_time: opts?.expires_time ? (0, js_iden3_core_1.getUnixTimestamp)(opts.expires_time) : undefined }; return request; } exports.createAuthorizationRequestWithMessage = createAuthorizationRequestWithMessage; /** * * Verifier is responsible for verification of JWZ / JWS packed messages with zero-knowledge proofs inside. * * @public * @class Verifier */ class Verifier { /** * Creates an instance of the Verifier. * @private * @param {Resolvers} resolvers - state resolvers instances * @param {VerifierSuiteParams} params - suite for verification */ constructor(stateResolver, params) { this.schemaLoader = params.documentLoader; this.stateResolver = stateResolver; this.packageManager = params.packageManager; this.circuitStorage = params.circuitStorage; this.prover = params.prover; } /** * Creates an instance of the Verifier. * @public * @param {VerifierParams} params - params to init verifier * @returns `Promise<Verifier>` */ static async newVerifier(params) { if (!params.suite) { const documentLoader = params.documentLoader ?? (0, js_sdk_1.cacheLoader)(params); const dirname = params?.circuitsDir ?? path_1.default.join(process.cwd(), 'circuits'); const circuitStorage = new js_sdk_1.FSCircuitStorage({ dirname }); params.suite = { documentLoader, circuitStorage, prover: new js_sdk_1.NativeProver(circuitStorage), packageManager: new js_sdk_1.PackageManager() }; const verifier = new Verifier(params.stateResolver, params.suite); await verifier.initPackers(params.didDocumentResolver); return verifier; } return new Verifier(params.stateResolver, params.suite); } // setPackageManager sets the package manager for the Verifier. setPackageManager(manager) { this.packageManager = manager; } // setPacker sets the custom packer manager for the Verifier. setPacker(packer) { return this.packageManager.registerPackers([packer]); } // setupAuthV2ZKPPacker sets the custom packer manager for the Verifier. async setupAuthV2ZKPPacker(circuitStorage) { if (!circuitStorage) { throw new Error('circuit storage is not defined'); } const authV2Set = await circuitStorage.loadCircuitData(js_sdk_1.CircuitId.AuthV2); if (!authV2Set.verificationKey) { throw new Error('verification key is not for authv2 circuit'); } const mapKey = js_jwz_1.proving.provingMethodGroth16AuthV2Instance.methodAlg.toString(); const provingParamMap = new Map(); const stateVerificationFn = async (circuitId, pubSignals) => { if (circuitId !== js_sdk_1.CircuitId.AuthV2) { throw new Error(`CircuitId is not supported ${circuitId}`); } const verifier = new authV2_1.AuthPubSignalsV2(pubSignals); await verifier.verifyStates(this.stateResolver); return true; }; const verificationFn = new js_sdk_1.VerificationHandlerFunc(stateVerificationFn); const verificationParamMap = new Map(); verificationParamMap.set(mapKey, { key: authV2Set.verificationKey, verificationFn }); const zkpPacker = new js_sdk_1.ZKPPacker(provingParamMap, verificationParamMap); return this.setPacker(zkpPacker); } // setupJWSPacker sets the JWS packer for the Verifier. setupJWSPacker(kms, documentResolver) { const jwsPacker = new js_sdk_1.JWSPacker(kms, documentResolver); return this.setPacker(jwsPacker); } verifyAuthRequest(request, opts) { if (!opts?.allowExpiredMessages) { (0, js_sdk_1.verifyExpiresTime)(request); } this.verifyProfile(request.type, request.body.accept); const groupIdValidationMap = {}; const requestScope = request.body.scope; for (const proofRequest of requestScope) { const groupId = proofRequest.query.groupId; if (groupId) { const existingRequests = groupIdValidationMap[groupId] ?? []; //validate that all requests in the group have the same schema, issuer and circuit for (const existingRequest of existingRequests) { if (existingRequest.query.type !== proofRequest.query.type) { throw new Error(`all requests in the group should have the same type`); } if (existingRequest.query.context !== proofRequest.query.context) { throw new Error(`all requests in the group should have the same context`); } const allowedIssuers = proofRequest.query.allowedIssuers; const existingRequestAllowedIssuers = existingRequest.query.allowedIssuers; if (!(allowedIssuers.includes('*') || allowedIssuers.every((issuer) => existingRequestAllowedIssuers.includes(issuer)))) { throw new Error(`all requests in the group should have the same issuer`); } } groupIdValidationMap[groupId] = [...(groupIdValidationMap[groupId] ?? []), proofRequest]; } } } /** * verifies zero knowledge proof response according to the proof request * @public * @param {AuthorizationResponseMessage} response - auth protocol response * @param {AuthorizationRequestMessage} proofRequest - auth protocol request * @param {VerifyOpts} opts - verification options * * @returns `Promise<void>` */ async verifyAuthResponse(response, request, opts) { if (!opts?.allowExpiredMessages) { (0, js_sdk_1.verifyExpiresTime)(request); } if ((request.body.message ?? '') !== (response.body.message ?? '')) { throw new Error('message for signing from request is not presented in response'); } if (request.from !== response.to) { throw new Error(`sender of the request is not a target of response - expected ${request.from}, given ${response.to}`); } this.verifyAuthRequest(request); const requestScope = request.body.scope; const groupIdToLinkIdMap = new Map(); // group requests by query group id for (const proofRequest of requestScope) { const groupId = proofRequest.query.groupId; const proofResp = response.body.scope.find((resp) => resp.id === proofRequest.id); if (!proofResp) { throw new Error(`proof is not given for requestId ${proofRequest.id}`); } const circuitId = proofResp.circuitId; if (circuitId !== proofRequest.circuitId) { throw new Error(`proof is not given for requested circuit expected: ${proofRequest.circuitId}, given ${circuitId}`); } const isValid = await this.prover.verify(proofResp, circuitId); if (!isValid) { throw new Error(`Proof with circuit id ${circuitId} and request id ${proofResp.id} is not valid`); } const CircuitVerifier = registry_1.Circuits.getCircuitPubSignals(circuitId); if (!CircuitVerifier) { throw new Error(`circuit ${circuitId} is not supported by the library`); } const params = proofRequest.params ?? {}; params.verifierDid = js_iden3_core_1.DID.parse(request.from); // verify query const verifier = new CircuitVerifier(proofResp.pub_signals); const pubSignals = await verifier.verifyQuery(proofRequest.query, this.schemaLoader, proofResp.vp, opts, params); // write linkId to the proof response const pubSig = pubSignals; if (pubSig.linkID && groupId) { groupIdToLinkIdMap.set(groupId, [ ...(groupIdToLinkIdMap.get(groupId) ?? []), { linkID: pubSig.linkID, requestId: proofResp.id } ]); } // verify states await verifier.verifyStates(this.stateResolver, opts); if (!response.from) { throw new Error(`proof response doesn't contain from field`); } // verify id ownership await verifier.verifyIdOwnership(response.from, BigInt(proofResp.id)); } // verify grouping links for (const [groupId, metas] of groupIdToLinkIdMap.entries()) { // check that all linkIds are the same if (metas.some((meta) => meta.linkID !== metas[0].linkID)) { throw new Error(`Link id validation failed for group ${groupId}, request linkID to requestIds info: ${JSON.stringify(metas)}`); } } } /** * verifies jwz token * @public * @param {string} tokenStr - token string * @param {VerifyOpts} opts - verification options * * @returns `Promise<Token>` */ async verifyJWZ(tokenStr, opts) { const token = await js_jwz_1.Token.parse(tokenStr); const key = (await this.circuitStorage.loadCircuitData(token.circuitId)) .verificationKey; if (!key) { throw new Error(`verification key is not found for circuit ${token.circuitId}`); } const isValid = await token.verify(key); if (!isValid) { throw new Error(`zero-knowledge proof of jwz token is not valid`); } const CircuitVerifier = registry_1.Circuits.getCircuitPubSignals(token.circuitId); if (!CircuitVerifier) { throw new Error(`circuit ${token.circuitId} is not supported by the library`); } // outputs unmarshaller const verifier = new CircuitVerifier(token.zkProof.pub_signals); // state verification await verifier.verifyStates(this.stateResolver, opts); return token; } /** * perform both verification of jwz / jws token and authorization request message * @public * @param {string} tokenStr - token string * @param {AuthorizationRequestMessage} request - auth protocol request * @param {VerifyOpts} opts - verification options * * @returns `Promise<AuthorizationResponseMessage>` */ async fullVerify(tokenStr, request, opts) { const msg = await this.packageManager.unpack(js_sdk_1.byteEncoder.encode(tokenStr)); if (request.body.accept?.length) { const acceptedMediaTypes = request.body.accept.map((accept) => (0, js_sdk_1.parseAcceptProfile)(accept).env); if (!acceptedMediaTypes.includes(msg.unpackedMediaType)) { throw new Error('response type is not in accept profiles of the request'); } } const response = msg.unpackedMessage; await this.verifyAuthResponse(response, request, opts); return response; } async initPackers(didResolver) { await this.setupAuthV2ZKPPacker(this.circuitStorage); // set default jws packer if packageManager is not present in options but did document resolver is. if (didResolver) { this.setupJWSPacker(new js_sdk_1.KMS(), didResolver); } } verifyProfile(messageType, profile) { if (!profile?.length) { return; } const supportedMediaTypes = []; for (const acceptProfile of profile) { const { env } = (0, js_sdk_1.parseAcceptProfile)(acceptProfile); if (this.packageManager.isProfileSupported(env, acceptProfile)) { supportedMediaTypes.push(env); } } if (!supportedMediaTypes.length) { throw new Error('no packer with profile which meets `accept` header requirements'); } } } exports.Verifier = Verifier;