UNPKG

@eecc/vc-verifier-rules

Version:

GS1 US Rules Verification Library for validating GS1 based verifiable credentials.

893 lines (874 loc) 42.1 kB
import Ajv from 'ajv/dist/2020.js'; import addFormats from 'ajv-formats'; import ajvErrors from 'ajv-errors'; // src/lib/rules-schema/genericCredentialSchema.ts var genericCredentialSchema = { "$id": "Generic-Schema", "$schema": "https://json-schema.org/draft/2020-12/schema", "version": "1.0.0", "description": "Generic Credential", "type": "object", "title": "genericCredentialSchema", "properties": void 0 }; // src/lib/rules-schema/gs1-chain-rules.ts var gs1CredentialChainRules = { genericCredentialSchema: { title: "genericCredentialSchema", extendsCredentialType: { type: [], rule: "" }, childCredential: void 0 }, GS1PrefixLicenseCredential: { title: "GS1PrefixLicenseCredential", extendsCredentialType: void 0, childCredential: { "type": ["GS1CompanyPrefixLicenseCredential", "GS1IdentificationKeyLicenseCredential"] } }, GS1CompanyPrefixLicenseCredential: { title: "GS1CompanyPrefixLicenseCredential", extendsCredentialType: { type: ["GS1PrefixLicenseCredential"], rule: "GS1PrefixLicenseCredential" }, childCredential: { "type": ["KeyCredential"] } }, KeyCredential: { title: "KeyCredential", extendsCredentialType: { type: ["GS1CompanyPrefixLicenseCredential", "GS1IdentificationKeyLicenseCredential"], rule: "GS1CompanyPrefixLicenseCredential" }, childCredential: { "type": ["OrganizationDataCredential", "ProductDataCredential"] } }, OrganizationDataCredential: { title: "OrganizationDataCredential", extendsCredentialType: { type: ["KeyCredential"], rule: "KeyCredential" }, childCredential: void 0 }, ProductDataCredential: { title: "ProductDataCredential", extendsCredentialType: { type: ["KeyCredential"], rule: "KeyDataCredential" }, childCredential: void 0 } }; // src/lib/utility/text-util.ts new TextEncoder(); var decoder = new TextDecoder(); // src/lib/schema/gs1-schema-extention-types.ts var gs1GenericSchemaGS1 = { "$id": "GS1-Generoc-Schema", "$schema": "https://json-schema.org/draft/2020-12/schema", "version": "1.0.0", "title": "GS1GenericSchema", "description": "A Generic GS1 Schema used when a specific schema is not available", "type": "object", "properties": { "credentialSubject": {} } }; var emptyJsonSchemaSubjectOnly = { "credentialSubject": {} }; var gs1AltLicenseValidationRules = { "altLicenseValidation": true }; var gs1DigitalLinkRules = { "digitalLink": true }; var gs1DigitalLinkSameAsRules = { "digitalLinkSameAs": true }; // src/lib/get-credential-type.ts var GS1_PREFIX_LICENSE_CREDENTIAL = "GS1PrefixLicenseCredential"; var GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL = "GS1CompanyPrefixLicenseCredential"; var KEY_CREDENTIAL = "KeyCredential"; var ORGANIZATION_DATA_CREDENTIAL = "OrganizationDataCredential"; var PRODUCT_DATA_CREDENTIAL = "ProductDataCredential"; var GS1_IDENTIFICATION_KEY_LICENSE_CREDENTIAL = "GS1IdentificationKeyLicenseCredential"; var GS1CredentialTypes = [ GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL, KEY_CREDENTIAL, ORGANIZATION_DATA_CREDENTIAL, PRODUCT_DATA_CREDENTIAL, GS1_PREFIX_LICENSE_CREDENTIAL, GS1_IDENTIFICATION_KEY_LICENSE_CREDENTIAL ]; var UNKNOWN_VALUE = "unknown"; var GS1CredentialSchema = [ { name: GS1_PREFIX_LICENSE_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/prefix" }, { name: GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/companyprefix" }, { name: KEY_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/key" }, { name: ORGANIZATION_DATA_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/organizationdata" }, { name: PRODUCT_DATA_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/productdata" }, { name: GS1_IDENTIFICATION_KEY_LICENSE_CREDENTIAL, schemaId: "https://id.gs1.org/vc/schema/v1/identificationkey" } ]; var getCredentialType = (credentialTypesSource) => { if (!credentialTypesSource) return { name: UNKNOWN_VALUE, schemaId: "" }; const credentialTypes = credentialTypesSource.filter((credentialType) => GS1CredentialTypes.includes(credentialType)); if (credentialTypes.length === 1) { const credentialType = GS1CredentialSchema.find((credential) => credential.name === credentialTypes[0]); if (!credentialType) { return { name: UNKNOWN_VALUE, schemaId: "" }; } return credentialType; } else if (credentialTypes.length > 1) { return { name: UNKNOWN_VALUE, schemaId: "" }; } return { name: UNKNOWN_VALUE, schemaId: "" }; }; var getCredentialRuleSchemaChain = function(credential) { if (!credential) { throw new Error("Credential is undefined"); } const credentialType = getCredentialType(credential.type); switch (credentialType.name) { case GS1_PREFIX_LICENSE_CREDENTIAL: return gs1CredentialChainRules.GS1PrefixLicenseCredential; case GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL: return gs1CredentialChainRules.GS1CompanyPrefixLicenseCredential; case KEY_CREDENTIAL: return gs1CredentialChainRules.KeyCredential; case ORGANIZATION_DATA_CREDENTIAL: return gs1CredentialChainRules.OrganizationDataCredential; case PRODUCT_DATA_CREDENTIAL: return gs1CredentialChainRules.ProductDataCredential; default: return gs1CredentialChainRules.genericCredentialSchema; } }; var getGS1JsonSchema = function(fullJsonSchemaValidationOn, standardSchema, gs1RulesSchema) { const schemaToValidate = { ...standardSchema }; if (!fullJsonSchemaValidationOn && gs1RulesSchema != null) { schemaToValidate.properties = { ...emptyJsonSchemaSubjectOnly }; } if (gs1RulesSchema != null) { schemaToValidate.properties.credentialSubject = { ...schemaToValidate.properties?.credentialSubject, ...gs1RulesSchema }; } return schemaToValidate; }; var getGS1JsonSchemaForV1 = function(jsonSchema) { const { ...v1Schema } = jsonSchema; v1Schema["$id"] = jsonSchema["$id"] + "-V1"; v1Schema.required = []; v1Schema.properties = {}; v1Schema.properties.credentialSubject = jsonSchema.properties.credentialSubject; return v1Schema; }; var callResolverToGetJsonSchema = function(schemaLoader, credentialType) { if (schemaLoader) { const credentialSchemaContent = schemaLoader(credentialType.schemaId); if (credentialSchemaContent && credentialSchemaContent.length > 0) { const schemaContent = decoder.decode(credentialSchemaContent); const parsedSchemaContent = JSON.parse(schemaContent); return parsedSchemaContent; } } return gs1GenericSchemaGS1; }; var getCredentialRuleSchema = function(schemaLoader, credential, fullJsonSchemaValidationOn = true) { if (!credential) { throw new Error("Credential can not be undefined."); } if (credential.type === void 0) { throw new Error("Credential type can not be undefined."); } const credentialType = getCredentialType(credential.type); const credentialSchema = callResolverToGetJsonSchema(schemaLoader, credentialType); const starterSchema = credential.proof !== void 0 ? getGS1JsonSchemaForV1(credentialSchema) : credentialSchema; switch (credentialType.name) { case GS1_PREFIX_LICENSE_CREDENTIAL: return getGS1JsonSchema(fullJsonSchemaValidationOn, starterSchema, gs1AltLicenseValidationRules); case GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL: return getGS1JsonSchema(fullJsonSchemaValidationOn, starterSchema, gs1AltLicenseValidationRules); case KEY_CREDENTIAL: return getGS1JsonSchema(fullJsonSchemaValidationOn, starterSchema, gs1DigitalLinkRules); case ORGANIZATION_DATA_CREDENTIAL: return getGS1JsonSchema(fullJsonSchemaValidationOn, starterSchema, gs1DigitalLinkSameAsRules); case PRODUCT_DATA_CREDENTIAL: return getGS1JsonSchema(fullJsonSchemaValidationOn, starterSchema, gs1DigitalLinkSameAsRules); default: return genericCredentialSchema; } }; // src/lib/engine/gs1-credential-errors.ts var invalidGS1CredentialTypes = { code: "GS1-100", rule: "The type of this license credential is not in the list of valid license credential types." }; var invalidExtendedCredentialMissing = { code: "GS1-120", rule: "Required extended license credential is missing to validate GS1 Extended Credentials Chain." }; var invalidRootCredentialType = { code: "GS1-130", rule: "The root credential type muts be a GS1PrefixLicenseCredential." }; var invalidIssueForPrefixLicense = { code: "GS1-140", rule: "The issuer of prefix license credential does not match the expected value." }; var invalidIssuer = { code: "GS1-150", rule: "The issuer of this license credential does not match the expected value." }; var invalidLicenseValueStartPrefix = { code: "GS1-201", rule: "License value does not start with the correct prefix value." }; var invalidLicenseValueFormat = { code: "GS1-202", rule: "The license value format is not valid." }; var invalidAlternativeLicenseValue = { code: "GS1-203", rule: "The alternative license value has not been specified." }; var invalidAlternativeLicenseNotCompatible = { code: "GS1-204", rule: "The alternative license value is not compatible with the license value." }; var invalidAlternativeLicenseNotSupported = { code: "GS1-205", rule: "An alternative license value is not supported." }; var invalidIssueSubject = { code: "GS1EX-212", rule: "License value does not start with the correct prefix value." }; var invalidGS1DigitalLink = { code: "GS1-213", rule: "Credential Subject Id must be a GS1 Digital Link." }; var invalidGS1DigitalLink_sameAs = { code: "GS1-214", rule: "Credential Subject sameAs must be a GS1 Digital Link." }; var dataMissingToValidateCredentialChain = { code: "GS1-300", rule: "One or more subject fields are missing or invalid. Can not validate credential chain." }; var dataMismatchBetweenDataKeyCredential = { code: "GS1-320", rule: "The data credential GS1 Digital Link does not match the Id in the Key Credential." }; var unsupportedCredentialChain = { code: "GS1-330", rule: "The credential chain is not supported." }; var verificationErrorCode = "VC-100"; var errorResolveCredentialCode = "GS1-010"; // src/lib/rules-definition/subject/check-credential-license.ts async function checkCredentialLicenseValue(credentialSubject, licenseLength = { miniumLength: 3, maximumLength: 14 }) { if (!credentialSubject?.licenseValue) { return { verified: false, rule: invalidLicenseValueFormat }; } const value = credentialSubject.licenseValue; if (isNaN(+value)) { return { verified: false, rule: invalidLicenseValueFormat }; } if (value.length < licenseLength.miniumLength || value.length > licenseLength.maximumLength) { return { verified: false, rule: invalidLicenseValueFormat }; } return { verified: true }; } async function checkPrefixCredentialLicenseValue(credentialSubject) { return checkCredentialLicenseValue(credentialSubject, { miniumLength: 2, maximumLength: 4 }); } // src/lib/rules-definition/subject/check-credential-subject-Id-digital-link.ts function checkForGS1DigitalLink(value, validationRule, ignoreNull) { if (!value) { if (ignoreNull) { return { verified: true }; } return { verified: false, rule: validationRule }; } const gs1DigitalLinkResult = parseGS1DigitalLink(value); return gs1DigitalLinkResult.isValid ? { verified: true } : { verified: false, rule: validationRule }; } function checkCredentialSameAsDigitalLink(credentialSubject) { return checkForGS1DigitalLink(credentialSubject?.sameAs, invalidGS1DigitalLink_sameAs, true); } function checkCredentialSubjectIdDigitalLink(credentialSubject) { return checkForGS1DigitalLink(credentialSubject?.id, invalidGS1DigitalLink, false); } var GS1_DIGITAL_LINK_GTIN = "01"; var GS1_DIGITAL_LINK_GLN = "254"; var GS1_DIGITAL_LINK_PARTYGLN = "417"; function gs1DigitalLinkType(typeValue) { if (typeValue === GS1_DIGITAL_LINK_GTIN) { return "GTIN"; } if (typeValue === GS1_DIGITAL_LINK_GLN || typeValue === GS1_DIGITAL_LINK_PARTYGLN) { return "GLN"; } return "Unknown"; } function parseGS1DigitalLink(value) { const ulrValue = value instanceof URL ? value.toString() : value ? value : ""; if (value != null) { const subjectIdNoProtocol = ulrValue.replace("https://", ""); const subjectIdParsed = subjectIdNoProtocol.split("/"); if (subjectIdParsed.length >= 3) { return { isValid: true, type: gs1DigitalLinkType(subjectIdParsed[1]), originalValue: ulrValue, parsedValue: subjectIdParsed[2], otherUriElements: subjectIdParsed.slice(3) }; } } return { isValid: false, originalValue: ulrValue, type: "Unknown" }; } // src/lib/utility/jwt-utils.ts var getDecodedPresentation = function getDecodedJwt(token) { const jwt = atob(token.split(".")[1]); const jwtPresentation = JSON.parse(jwt); const decodedPresentation = { ...jwtPresentation }; decodedPresentation.verifiableCredential = []; jwtPresentation.verifiableCredential.forEach((vc) => { if (typeof vc === "object" && vc.id && typeof vc.id === "string" && !("credentialSubject" in vc && vc.id.split(";")[0] === "data:application/vc+jwt")) { const parseOutToken = vc.id.split(";")[1]; const jwt2 = atob(parseOutToken.split(".")[1]); const jsonJwt = JSON.parse(jwt2); decodedPresentation.verifiableCredential.push(jsonJwt); } else { decodedPresentation.verifiableCredential.push(vc); } }); return decodedPresentation; }; var getDecodedCredential = function getDecodedJwt2(token) { const jwt = atob(token.split(".")[1]); const jwtCredential = JSON.parse(jwt); return jwtCredential; }; var normalizeCredential = function(credential) { if (typeof credential === "string") { return getDecodedCredential(credential); } if (typeof credential === "object" && credential.id && typeof credential.id === "string" && credential.id.startsWith("data:application/vc+jwt")) { const jwtPart = credential.id.split(";")[1]; if (jwtPart) { return getDecodedCredential(jwtPart); } } return credential; }; var normalizePresentation = function(presentation) { if (typeof presentation === "string") { return getDecodedPresentation(presentation); } return presentation; }; // src/lib/rules-definition/chain/shared-extended.ts function getCredentialIssuer(credential) { if (!credential) { return ""; } return typeof credential.issuer === "string" ? credential.issuer : credential.issuer.id; } async function checkIssuerToSubjectId(credential, extendedCredentialSubject) { const credentialIssuer = getCredentialIssuer(credential); if (credentialIssuer !== extendedCredentialSubject?.id) { return { verified: false, rule: invalidIssueSubject }; } return { verified: true }; } function checkCredentialChainIssuers(credentialToCheck) { if (!credentialToCheck) { throw new Error("Credential Chain Issuers are not defined."); } if (!credentialToCheck.dataCredential || !credentialToCheck.keyCredential || !credentialToCheck.companyPrefix) { return false; } const organizationCredentialIssuer = getCredentialIssuer(normalizeCredential(credentialToCheck.dataCredential)); const keyCredentialIssuer = getCredentialIssuer(normalizeCredential(credentialToCheck.keyCredential)); const companyPrefixCredentialIssuer = getCredentialIssuer(normalizeCredential(credentialToCheck.companyPrefix)); const companyPrefixSubjectID = normalizeCredential(credentialToCheck.companyPrefix)?.credentialSubject.id; if (!organizationCredentialIssuer || !keyCredentialIssuer || !companyPrefixCredentialIssuer || !companyPrefixSubjectID) { return false; } if (organizationCredentialIssuer === keyCredentialIssuer) { if (keyCredentialIssuer !== companyPrefixCredentialIssuer) { if (keyCredentialIssuer !== companyPrefixSubjectID) { return false; } } } else { return false; } return true; } function checkCredentialIssuers(credential, credentialToCompare) { const credentialIssuer = getCredentialIssuer(credential); const credentialToCompareIssuer = getCredentialIssuer(credentialToCompare); if (credentialIssuer !== credentialToCompareIssuer) { return false; } return true; } function compareLicenseValue(licenseValue, prefixValue) { const prefixPosition = licenseValue.indexOf(prefixValue); return licenseValue?.startsWith(prefixValue, prefixPosition); } // src/lib/rules-definition/chain/validate-extended-company-prefix.ts async function validateExtendedCompanyPrefixCredential(credentialType, credentialChain) { const gs1CredentialCheck = { credentialId: normalizeCredential(credentialChain.credential).id, credentialName: credentialType, verified: true, errors: [] }; const credential = normalizeCredential(credentialChain.credential); const credentialSubject = credential.credentialSubject; const extendedCredential = credentialChain.extendedCredentialChain?.credential ? normalizeCredential(credentialChain.extendedCredentialChain?.credential) : void 0; if (!extendedCredential) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(invalidExtendedCredentialMissing); return gs1CredentialCheck; } const extendedCredentialSubject = extendedCredential?.credentialSubject; const issuerResult = await checkIssuerToSubjectId(credential, extendedCredentialSubject); if (!issuerResult.verified) { if (!checkCredentialIssuers(credential, extendedCredential)) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(invalidIssuer); } } const keyValue = parseGS1DigitalLink(credentialSubject.id); const companyPrefixLicenseValue = extendedCredentialSubject.licenseValue; if (!keyValue.parsedValue || !compareLicenseValue(keyValue.parsedValue, companyPrefixLicenseValue)) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(invalidLicenseValueFormat); } return gs1CredentialCheck; } // src/lib/rules-definition/chain/validate-extended-license-prefix.ts var GS1_GLOBAL_DID = "did:web:id.gs1.org"; async function compareLicenseLengthsToExtended(credentialSubject, extendedCredentialSubject) { const licenseValue = credentialSubject?.licenseValue; const extendedLicenseValue = extendedCredentialSubject?.licenseValue; if (!licenseValue || !extendedLicenseValue) { return { verified: false, rule: invalidLicenseValueFormat }; } if (compareLicenseValue(licenseValue, extendedLicenseValue)) { if (licenseValue.length <= extendedLicenseValue.length) { return { verified: false, rule: invalidLicenseValueStartPrefix }; } } else { return { verified: false, rule: invalidLicenseValueFormat }; } return { verified: true }; } async function validateExtendedLicensePrefix(credentialType, credentialChain) { const gs1CredentialCheck = { credentialId: normalizeCredential(credentialChain.credential).id, credentialName: credentialType, verified: true, errors: [] }; const credential = normalizeCredential(credentialChain.credential); const credentialSubject = credential.credentialSubject; const extendedCredential = credentialChain.extendedCredentialChain?.credential ? normalizeCredential(credentialChain.extendedCredentialChain?.credential) : void 0; const extendedCredentialSubject = extendedCredential?.credentialSubject; if (!extendedCredential) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(invalidExtendedCredentialMissing); return gs1CredentialCheck; } const extendedCredentialIssuer = getCredentialIssuer(extendedCredential); if (extendedCredentialIssuer !== GS1_GLOBAL_DID) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(invalidIssueForPrefixLicense); return gs1CredentialCheck; } const issuerResult = await checkIssuerToSubjectId(credential, extendedCredentialSubject); if (!issuerResult.verified && issuerResult.rule) { gs1CredentialCheck.errors.push(issuerResult.rule); } const compareLicenseLResult = await compareLicenseLengthsToExtended(credentialSubject, extendedCredentialSubject); if (!compareLicenseLResult.verified && compareLicenseLResult.rule) { gs1CredentialCheck.errors.push(compareLicenseLResult.rule); } if (gs1CredentialCheck.errors.length > 0) { gs1CredentialCheck.verified = false; } return gs1CredentialCheck; } // src/lib/rules-definition/chain/validate-extended-data-key.ts function setupDataCredentialChain(credentialChain) { const dataCredential = normalizeCredential(credentialChain.credential); const dataCredentialChain = { isValid: true, dataCredential, dataCredentialSubject: dataCredential.credentialSubject, KeyCredential: credentialChain.extendedCredentialChain?.credential ? { credential: normalizeCredential(credentialChain.extendedCredentialChain?.credential), extendedCredentialChain: credentialChain.extendedCredentialChain?.extendedCredentialChain } : void 0, keyCredentialSubject: credentialChain.extendedCredentialChain?.credential ? normalizeCredential(credentialChain.extendedCredentialChain?.credential)?.credentialSubject : void 0 }; dataCredentialChain.companyPrefixCredential = dataCredentialChain.KeyCredential?.extendedCredentialChain; dataCredentialChain.companyPrefixCredentialSubject = dataCredentialChain.companyPrefixCredential?.credential ? normalizeCredential(dataCredentialChain.companyPrefixCredential?.credential)?.credentialSubject : void 0; if (!dataCredentialChain.dataCredentialSubject || !dataCredentialChain.keyCredentialSubject || !dataCredentialChain.companyPrefixCredentialSubject) { dataCredentialChain.isValid = false; } return dataCredentialChain; } function validateDataToKeyCredential(keyCredentialSubject, dataCredentialSubject, valueToCheck = "") { if (keyCredentialSubject === void 0) { throw new Error("Key Credential Subject is not defined."); } if (dataCredentialSubject === void 0) { throw new Error("Data Credential Subject is not defined."); } const keyDigitalLink = parseGS1DigitalLink(keyCredentialSubject.id); const dataCredentialId = dataCredentialSubject.sameAs ? dataCredentialSubject.sameAs : dataCredentialSubject.id; const dataCredentialIdDigitalLink = parseGS1DigitalLink(dataCredentialId); if (keyDigitalLink.isValid && dataCredentialIdDigitalLink.isValid) { if (keyDigitalLink.parsedValue !== dataCredentialIdDigitalLink.parsedValue) { return false; } } else { return false; } if (valueToCheck !== "") { return keyDigitalLink.parsedValue === valueToCheck; } return true; } function checkDataCredentialIssuerChain(dataCredentialChain) { if (dataCredentialChain === void 0) throw new Error("Data Credential Chain is not defined."); if (dataCredentialChain.dataCredential === void 0) throw new Error("dataCredential is required to validate GS1 Credential Chain."); if (dataCredentialChain.KeyCredential === void 0) throw new Error("KeyCredential is required to validate GS1 Credential Chain."); if (dataCredentialChain.companyPrefixCredential === void 0) throw new Error("companyPrefixCredential is required to validate GS1 Credential Chain."); return checkCredentialChainIssuers({ dataCredential: dataCredentialChain.dataCredential, keyCredential: dataCredentialChain.KeyCredential.credential, companyPrefix: dataCredentialChain.companyPrefixCredential.credential }); } async function validateExtendedKeyDataCredential(credentialType, credentialChain) { const gs1CredentialCheck = { credentialId: normalizeCredential(credentialChain.credential).id, credentialName: credentialType, verified: false, errors: [] }; const dataCredentialChain = setupDataCredentialChain(credentialChain); if (!dataCredentialChain.isValid) { gs1CredentialCheck.errors.push(dataMissingToValidateCredentialChain); return gs1CredentialCheck; } const checkDataToKey = validateDataToKeyCredential(dataCredentialChain.keyCredentialSubject, dataCredentialChain.dataCredentialSubject); if (!checkDataToKey) { gs1CredentialCheck.errors.push(dataMismatchBetweenDataKeyCredential); } if (!checkDataCredentialIssuerChain(dataCredentialChain)) { gs1CredentialCheck.errors.push(invalidIssuer); } gs1CredentialCheck.verified = gs1CredentialCheck.errors.length === 0; return gs1CredentialCheck; } async function validateExtendedKeyCredential(credentialType, credentialChain) { const gs1CredentialCheck = { credentialId: normalizeCredential(credentialChain.credential).id, credentialName: credentialType, verified: false, errors: [] }; const dataCredentialChain = setupDataCredentialChain(credentialChain); if (!dataCredentialChain.isValid) { gs1CredentialCheck.errors.push(dataMissingToValidateCredentialChain); return gs1CredentialCheck; } const checkDataToKey = validateDataToKeyCredential( dataCredentialChain.keyCredentialSubject, dataCredentialChain.dataCredentialSubject ); if (!checkDataToKey) { gs1CredentialCheck.errors.push(dataMismatchBetweenDataKeyCredential); } if (!checkDataCredentialIssuerChain(dataCredentialChain)) { gs1CredentialCheck.errors.push(invalidIssuer); } gs1CredentialCheck.verified = gs1CredentialCheck.errors.length === 0; return gs1CredentialCheck; } // src/lib/rules-definition/rules-manager.ts var rulesEngineManager = {}; rulesEngineManager.prefixLicense = checkPrefixCredentialLicenseValue; rulesEngineManager.GS1PrefixLicenseCredential = validateExtendedLicensePrefix; rulesEngineManager.GS1CompanyPrefixLicenseCredential = validateExtendedCompanyPrefixCredential; rulesEngineManager.KeyCredential = validateExtendedKeyCredential; rulesEngineManager.KeyDataCredential = validateExtendedKeyDataCredential; async function resolveExternalCredential(externalCredentialLoader, verifiablePresentation, url) { try { if (verifiablePresentation) { const presentationVerifiableCredential = verifiablePresentation.verifiableCredential; if (Array.isArray(presentationVerifiableCredential)) { const credential = presentationVerifiableCredential.find((q) => q.id === url); if (credential !== void 0) { return { credential, inPresentation: true }; } } else { if (presentationVerifiableCredential.id === url) { return { credential: presentationVerifiableCredential, inPresentation: true }; } } } if (!url) { throw new Error(`External Credential "${url}" can not be resolved.`); } const externalResult = await externalCredentialLoader(url); return { credential: externalResult, inPresentation: false }; } catch (e) { return { credential: void 0, inPresentation: false, error: `External Credential "${url}" can not be resolved.` }; } } async function buildCredentialChain(externalCredentialLoader, verifiablePresentation, credential) { const credentialSubject = normalizeCredential(credential)?.credentialSubject; const credentialSchema = getCredentialRuleSchemaChain(normalizeCredential(credential)); const credentialSubjectSchemaRule = credentialSchema; const credentialChain = { credential, inPresentation: true, schema: credentialSchema, credentialSubjectSchema: credentialSubjectSchemaRule, extendedCredentialChain: void 0 }; if (credentialSubjectSchemaRule) { const extendsCredentialMetaData = credentialSubjectSchemaRule.extendsCredentialType; if (extendsCredentialMetaData) { const extendedCredentialValue = credentialSubject.extendsCredential || credentialSubject.keyAuthorization; const extendedCredentialResult = await resolveExternalCredential(externalCredentialLoader, verifiablePresentation, extendedCredentialValue); if (extendedCredentialResult.credential) { const extendedCredentialChain = await buildCredentialChain(externalCredentialLoader, verifiablePresentation, extendedCredentialResult.credential); if (extendedCredentialChain.credential) { extendedCredentialChain.inPresentation = extendedCredentialResult.inPresentation; credentialChain.extendedCredentialChain = extendedCredentialChain; } else { credentialChain.error = extendedCredentialResult.error; } } else { credentialChain.error = extendedCredentialResult.error; } } } return credentialChain; } async function validateCredentialChain(externalCredentialVerification, credentialChain, validateChain) { const credential = credentialChain.credential; const decodedCredential = normalizeCredential(credential); const credentialSchema = credentialChain.schema; const credentialSchemaSubject = credentialSchema.extendsCredentialType; const extendedCredential = credentialChain.extendedCredentialChain?.credential; const decodedExtendedCredential = extendedCredential ? normalizeCredential(extendedCredential) : void 0; const gs1CredentialCheck = { credentialId: decodedCredential.id, credentialName: credentialSchema.title, verified: true, errors: [] }; if (!credentialChain.inPresentation) { const checkCredentialResult = await externalCredentialVerification(credentialChain.credential); if (!checkCredentialResult.verified) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors = gs1CredentialCheck.errors.concat(checkCredentialResult.errors); } } if (!decodedExtendedCredential) { return gs1CredentialCheck; } const extendedCredentialSchema = getCredentialRuleSchemaChain(decodedExtendedCredential); const extendedCredentialType = extendedCredentialSchema.title; const parentInvalid = credentialSchemaSubject?.type.includes(extendedCredentialType); if (!parentInvalid) { gs1CredentialCheck.errors.push(invalidGS1CredentialTypes); } const credentialType = getCredentialType(decodedCredential.type); const childIsValid = extendedCredentialSchema?.childCredential?.type.includes(credentialType.name); if (!childIsValid) { gs1CredentialCheck.errors.push(invalidGS1CredentialTypes); } const extendedCredentialType_rule = credentialSchemaSubject?.rule; if (!extendedCredentialType_rule || typeof rulesEngineManager[extendedCredentialType_rule] === "undefined") { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors.push(unsupportedCredentialChain); return gs1CredentialCheck; } if (validateChain) { const extendedCredentialValidationResult = await rulesEngineManager[extendedCredentialType_rule](credentialType, credentialChain); if (!extendedCredentialValidationResult.verified) { gs1CredentialCheck.errors = gs1CredentialCheck.errors.concat(extendedCredentialValidationResult.errors); } if (gs1CredentialCheck.errors.length > 0) { gs1CredentialCheck.verified = false; } } if (credentialChain.extendedCredentialChain?.extendedCredentialChain) { const validateExtendedCredentialResult = await validateCredentialChain(externalCredentialVerification, credentialChain.extendedCredentialChain, false); if (!credentialChain.extendedCredentialChain?.extendedCredentialChain.inPresentation) { const resolvedCredentialMetaData = credentialChain.extendedCredentialChain?.extendedCredentialChain; gs1CredentialCheck.resolvedCredential = { credentialId: normalizeCredential(resolvedCredentialMetaData?.credential)?.id, credentialName: resolvedCredentialMetaData?.schema?.title, verified: validateExtendedCredentialResult.verified, errors: validateExtendedCredentialResult.errors }; } if (!validateExtendedCredentialResult.verified) { gs1CredentialCheck.errors = gs1CredentialCheck.errors.concat(validateExtendedCredentialResult.errors); } } else { if (extendedCredentialType !== GS1_PREFIX_LICENSE_CREDENTIAL) { gs1CredentialCheck.errors.push(invalidRootCredentialType); } if (credentialChain.extendedCredentialChain && credentialChain.extendedCredentialChain.credentialSubjectSchema) { const validateExtendedCredentialResult = await validateCredentialChain(externalCredentialVerification, credentialChain.extendedCredentialChain, false); if (!credentialChain.extendedCredentialChain.inPresentation) { const resolvedCredentialMetaData = credentialChain.extendedCredentialChain; gs1CredentialCheck.resolvedCredential = { credentialId: normalizeCredential(resolvedCredentialMetaData?.credential)?.id, credentialName: resolvedCredentialMetaData?.schema?.title, verified: validateExtendedCredentialResult.verified, errors: validateExtendedCredentialResult.errors }; } if (!validateExtendedCredentialResult.verified) { gs1CredentialCheck.verified = false; gs1CredentialCheck.errors = gs1CredentialCheck.errors.concat(validateExtendedCredentialResult.errors); } } } return gs1CredentialCheck; } // src/lib/rules-definition/subject/check-credential-alternative-license.ts function checkCredentialAlternativeLicenseValue(credentialSubject) { const value = credentialSubject.licenseValue; const altValue = credentialSubject.alternativeLicenseValue; if (!value) { return { verified: false, rule: invalidLicenseValueFormat }; } if (value.startsWith("0")) { if (!altValue) { return { verified: false, rule: invalidAlternativeLicenseValue }; } if (value !== "0" + altValue) { return { verified: false, rule: invalidAlternativeLicenseNotCompatible }; } } else { if (altValue !== void 0) { return { verified: false, rule: invalidAlternativeLicenseNotSupported }; } } return { verified: true }; } // src/lib/schema/ajv-gs1-extension.ts var ajv = new Ajv({ strict: false, allErrors: true }); addFormats(ajv); ajvErrors(ajv); var compileSchema = function(schemaToValidate) { const maybeExistingSchema = ajv.getSchema(schemaToValidate.$id); let compiledSchemaValidator = maybeExistingSchema; if (compiledSchemaValidator === void 0) { compiledSchemaValidator = ajv.compile(schemaToValidate); } return compiledSchemaValidator; }; ajv.addKeyword({ keyword: "altLicenseValidation", type: "object", errors: true, async: false, validate: function customRuleValidation(schema, data, parentSchema, dataCxt) { const gs1RuleCheckResult = checkCredentialAlternativeLicenseValue(data); if (!gs1RuleCheckResult.verified) { customRuleValidation.errors = createJsonSchemaError("altLicenseValidation", gs1RuleCheckResult, dataCxt); } return gs1RuleCheckResult.verified; } }); ajv.addKeyword({ keyword: "digitalLink", type: "object", errors: true, async: false, validate: function customRuleValidation2(schema, data, parentSchema, dataCxt) { const gs1RuleCheckResult = checkCredentialSubjectIdDigitalLink(data); if (!gs1RuleCheckResult.verified) { customRuleValidation2.errors = createJsonSchemaError("digitalLink", gs1RuleCheckResult, dataCxt); } return gs1RuleCheckResult.verified; } }); ajv.addKeyword({ keyword: "digitalLinkSameAs", type: "object", errors: true, async: false, validate: function customRuleValidation3(schema, data, parentSchema, dataCxt) { const gs1RuleCheckResult = checkCredentialSameAsDigitalLink(data); if (!gs1RuleCheckResult.verified) { customRuleValidation3.errors = createJsonSchemaError("digitalLink", gs1RuleCheckResult, dataCxt); } return gs1RuleCheckResult.verified; } }); var createJsonSchemaError = function(keyword, gs1RuleCheckResult, dataCxt) { const errors = []; errors.push({ instancePath: dataCxt?.instancePath, schemaPath: "", keyword, params: { keyword, isGS1Error: true, gs1Rule: gs1RuleCheckResult.rule }, message: gs1RuleCheckResult.rule?.rule }); return errors; }; // src/lib/schema/validate-schema.ts async function checkSchema(schemaToValidate, credential) { const gs1CredentialCheck = { credentialId: credential.id, credentialName: schemaToValidate.title ? schemaToValidate.title : "unknown", verified: true, errors: [] }; const credentialToValidate = { ...credential, credentialSubject: { ...credential.credentialSubject } }; const ajvSchenma = compileSchema(schemaToValidate); const valid = ajvSchenma(credentialToValidate); if (!valid) { gs1CredentialCheck.verified = false; ajvSchenma.errors?.forEach((error) => { const jsonSchemaError = error.params; if (jsonSchemaError.isGS1Error && jsonSchemaError.gs1Rule !== void 0) { gs1CredentialCheck.errors.push(jsonSchemaError.gs1Rule); } else { const jsonSchemaErrorRule = { code: errorResolveCredentialCode, rule: `${error.instancePath} ${error.message}` }; gs1CredentialCheck.errors.push(jsonSchemaErrorRule); } }); } return gs1CredentialCheck; } // src/lib/gs1-verification-service.ts async function checkGS1Credentials(validatorRequest, verifiablePresentation, credential) { const decodedCredential = normalizeCredential(credential); const decodedPresentation = normalizePresentation(verifiablePresentation); const credentialSubject = decodedCredential?.credentialSubject; if (!decodedCredential || !credentialSubject) { throw new Error("No Credential in Presentation"); } if (!validatorRequest || !validatorRequest.gs1DocumentResolver) { throw new Error("Document Resolver Callback must be provided to validate GS1 Credentials"); } const externalCredentialLoader = validatorRequest.gs1DocumentResolver.externalCredentialLoader; const externalCredentialVerification = validatorRequest.gs1DocumentResolver.externalCredentialVerification; const jsonSchemaLoader = validatorRequest.gs1DocumentResolver.externalJsonSchemaLoader; const credentialSchema = getCredentialRuleSchema(jsonSchemaLoader, decodedCredential, validatorRequest.fullJsonSchemaValidationOn); if (credentialSchema.$id === genericCredentialSchema.$id) { return { credentialId: decodedCredential.id, credentialName: "unknown", verified: true, errors: [] }; } const gs1CredentialCheck = { credentialId: decodedCredential.id, credentialName: credentialSchema.title ? credentialSchema.title : "unknown", verified: true, errors: [] }; const schemaCheckResult = await checkSchema(credentialSchema, decodedCredential); if (!schemaCheckResult.verified) { gs1CredentialCheck.errors = schemaCheckResult.errors; } const credentialChain = await buildCredentialChain(externalCredentialLoader, decodedPresentation, decodedCredential); if (!credentialChain.error) { const extendedCredentialResult = await validateCredentialChain(externalCredentialVerification, credentialChain, true); gs1CredentialCheck.resolvedCredential = extendedCredentialResult.resolvedCredential; if (!extendedCredentialResult.verified) { gs1CredentialCheck.errors = gs1CredentialCheck.errors.concat(extendedCredentialResult.errors); } } else { gs1CredentialCheck.errors.push({ code: errorResolveCredentialCode, rule: credentialChain.error }); } if (gs1CredentialCheck.errors.length > 0) { gs1CredentialCheck.verified = false; } return gs1CredentialCheck; } async function checkGS1CredentialWithoutPresentation(validatorRequest, verifiableCredential) { const decodedCredential = normalizeCredential(verifiableCredential); const verifiablePresentation = { verifiableCredential: decodedCredential }; return await checkGS1Credentials(validatorRequest, verifiablePresentation, verifiableCredential); } async function checkGS1CredentialPresentationValidation(validatorRequest, verifiablePresentation) { const gs1CredentialCheck = { verified: true, result: [] }; const decodedPresentation = normalizePresentation(verifiablePresentation); const presentationCredentials = decodedPresentation.verifiableCredential; if (Array.isArray(presentationCredentials)) { if (!validatorRequest.gs1DocumentResolver || !validatorRequest.gs1DocumentResolver.externalCredentialLoader) { throw new Error("Validation Document Resolver Callback must be provided to validate GS1 Credentials"); } const externalCredentialLoader = validatorRequest.gs1DocumentResolver.externalCredentialLoader; const presentationToValidateVC = structuredClone(presentationCredentials); for (const credential of presentationCredentials) { const extendedCredentialValue = credential.credentialSubject.extendsCredential; const extendedCredentialResult = await resolveExternalCredential(externalCredentialLoader, decodedPresentation, extendedCredentialValue); if (extendedCredentialResult?.credential) { if (!extendedCredentialResult.inPresentation) { presentationToValidateVC.unshift(extendedCredentialResult.credential); } } } const presentationToValidate = structuredClone(decodedPresentation); presentationToValidate.verifiableCredential = presentationToValidateVC; for (const credential of presentationToValidate.verifiableCredential) { const credentialResult = await checkGS1Credentials(validatorRequest, presentationToValidate, credential); gs1CredentialCheck.result.push(credentialResult); if (!credentialResult.verified) { gs1CredentialCheck.verified = false; } } } else { const credentialResult = await checkGS1Credentials(validatorRequest, decodedPresentation, presentationCredentials); gs1CredentialCheck.result.push(credentialResult); if (!credentialResult.verified) { gs1CredentialCheck.verified = false; } if (credentialResult.resolvedCredential) { gs1CredentialCheck.result.push(credentialResult.resolvedCredential); } } return gs1CredentialCheck; } export { GS1_COMPANY_PREFIX_LICENSE_CREDENTIAL, GS1_IDENTIFICATION_KEY_LICENSE_CREDENTIAL, GS1_PREFIX_LICENSE_CREDENTIAL, KEY_CREDENTIAL, ORGANIZATION_DATA_CREDENTIAL, PRODUCT_DATA_CREDENTIAL, UNKNOWN_VALUE, checkGS1CredentialPresentationValidation, checkGS1CredentialWithoutPresentation, getCredentialRuleSchema, getCredentialRuleSchemaChain, getCredentialType, verificationErrorCode }; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.js.map