From 4a7c6b760dfed04c969f25b660d1c21864f59cd9 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 11:20:00 +0200 Subject: [PATCH 01/13] feat(car-6): hsm emulator to demonstrate key generation and transport. --- pnpm-lock.yaml | 331 +++++++++++++++++++-- test/hsm-emulator/README.md | 41 +++ test/hsm-emulator/hsm.http | 101 +++++++ test/hsm-emulator/package.json | 25 ++ test/hsm-emulator/src/app.ts | 181 ++++++++++++ test/hsm-emulator/src/card-hsm.ts | 459 ++++++++++++++++++++++++++++++ test/hsm-emulator/src/index.ts | 13 + test/hsm-emulator/src/logger.ts | 6 + test/hsm-emulator/tsconfig.json | 11 + 9 files changed, 1141 insertions(+), 27 deletions(-) create mode 100644 test/hsm-emulator/README.md create mode 100644 test/hsm-emulator/hsm.http create mode 100644 test/hsm-emulator/package.json create mode 100644 test/hsm-emulator/src/app.ts create mode 100644 test/hsm-emulator/src/card-hsm.ts create mode 100644 test/hsm-emulator/src/index.ts create mode 100644 test/hsm-emulator/src/logger.ts create mode 100644 test/hsm-emulator/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bfed84d19..35b218c141 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -746,6 +746,28 @@ importers: specifier: ^6.7.6 version: 6.7.6 + test/hsm-emulator: + dependencies: + fastify: + specifier: ^5.2.1 + version: 5.3.2 + pino: + specifier: ^9.6.0 + version: 9.6.0 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.14.15 + '@types/pg': + specifier: ^8.11.11 + version: 8.11.14 + tsx: + specifier: ^4.19.3 + version: 4.19.4 + typescript: + specifier: ^5.0.0 + version: 5.8.3 + test/integration: devDependencies: '@interledger/open-payments': @@ -3928,11 +3950,46 @@ packages: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + /@fastify/ajv-compiler@4.0.2: + resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + dev: false + /@fastify/busboy@2.1.1: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} dev: true + /@fastify/error@4.1.0: + resolution: {integrity: sha512-KeFcciOr1eo/YvIXHP65S94jfEEqn1RxTRBT1aJaHxY5FK0/GDXYozsQMMWlZoHgi8i0s+YtrLsgj/JkUUjSkQ==} + dev: false + + /@fastify/fast-json-stringify-compiler@5.0.3: + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + dependencies: + fast-json-stringify: 6.0.1 + dev: false + + /@fastify/forwarded@3.0.0: + resolution: {integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==} + dev: false + + /@fastify/merge-json-schemas@0.2.1: + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + dependencies: + dequal: 2.0.3 + dev: false + + /@fastify/proxy-addr@5.0.0: + resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==} + dependencies: + '@fastify/forwarded': 3.0.0 + ipaddr.js: 2.2.0 + dev: false + /@graphql-codegen/add@5.0.3(graphql@16.10.0): resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==} peerDependencies: @@ -5603,7 +5660,7 @@ packages: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.3 + semver: 7.7.1 dev: true /@npmcli/git@4.1.0: @@ -5616,7 +5673,7 @@ packages: proc-log: 3.0.0 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.6.3 + semver: 7.7.1 which: 3.0.1 transitivePeerDependencies: - bluebird @@ -7739,7 +7796,6 @@ packages: resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==} dependencies: undici-types: 5.26.5 - dev: true /@types/node@20.14.15: resolution: {integrity: sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==} @@ -7749,9 +7805,16 @@ packages: /@types/pg-pool@2.0.4: resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} dependencies: - '@types/pg': 8.6.1 + '@types/pg': 8.11.14 dev: false + /@types/pg@8.11.14: + resolution: {integrity: sha512-qyD11E5R3u0eJmd1lB0WnWKXJGA7s015nyARWljfz5DcX83TKAIlY+QrmvzQTsbIe+hkiFtkyL2gHC6qwF6Fbg==} + dependencies: + '@types/node': 20.14.15 + pg-protocol: 1.6.0 + pg-types: 4.0.2 + /@types/pg@8.6.1: resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} dependencies: @@ -7928,7 +7991,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8098,7 +8161,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8119,7 +8182,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.1 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8141,7 +8204,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.1 ts-api-utils: 1.0.1(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -8162,7 +8225,7 @@ packages: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript @@ -8182,7 +8245,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript @@ -8201,7 +8264,7 @@ packages: '@typescript-eslint/types': 7.5.0 '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) eslint: 8.57.1 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color - typescript @@ -8562,6 +8625,10 @@ packages: dependencies: event-target-shim: 5.0.1 + /abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -8641,6 +8708,17 @@ packages: dependencies: ajv: 8.17.1 + /ajv-formats@3.0.1(ajv@8.17.1): + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.17.1 + dev: false + /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -9055,7 +9133,7 @@ packages: unist-util-visit: 5.0.0 unstorage: 1.15.0 vfile: 6.0.3 - vite: 6.2.5(@types/node@18.11.9)(yaml@2.7.0) + vite: 6.2.5(@types/node@20.12.7)(yaml@2.7.0) vitefu: 1.0.6(vite@6.2.5) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -9154,7 +9232,7 @@ packages: unist-util-visit: 5.0.0 unstorage: 1.15.0 vfile: 6.0.3 - vite: 6.2.5(@types/node@18.11.9)(yaml@2.7.0) + vite: 6.2.5(@types/node@20.12.7)(yaml@2.7.0) vitefu: 1.0.6(vite@6.2.5) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 @@ -9257,6 +9335,13 @@ packages: dependencies: possible-typed-array-names: 1.0.0 + /avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + dependencies: + '@fastify/error': 4.1.0 + fastq: 1.19.1 + dev: false + /axe-core@4.10.2: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} @@ -9662,7 +9747,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.6.3 + semver: 7.7.1 dev: true /busboy@1.6.0: @@ -12197,7 +12282,6 @@ packages: /fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - dev: true /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -12232,6 +12316,17 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-json-stringify@6.0.1: + resolution: {integrity: sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==} + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + json-schema-ref-resolver: 2.0.1 + rfdc: 1.4.1 + dev: false + /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true @@ -12240,7 +12335,6 @@ packages: resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 - dev: true /fast-redact@3.1.2: resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} @@ -12263,11 +12357,37 @@ packages: engines: {node: '>= 4.9.1'} dev: true + /fastify@5.3.2: + resolution: {integrity: sha512-AIPqBgtqBAwkOkrnwesEE+dOyU30dQ4kh7udxeGVR05CRGwubZx+p2H8P0C4cRnQT0+EPK4VGea2DTL2RtWttg==} + dependencies: + '@fastify/ajv-compiler': 4.0.2 + '@fastify/error': 4.1.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.0.0 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.0.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 9.6.0 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.0.0 + semver: 7.7.1 + toad-cache: 3.7.0 + dev: false + /fastq@1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: reusify: 1.0.4 + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + dependencies: + reusify: 1.0.4 + dev: false + /fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} dependencies: @@ -12358,6 +12478,15 @@ packages: pkg-dir: 7.0.0 dev: true + /find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.1 + safe-regex2: 5.0.0 + dev: false + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -12647,6 +12776,12 @@ packages: get-intrinsic: 1.2.4 dev: true + /get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-tsconfig@4.5.0: resolution: {integrity: sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==} dev: true @@ -13579,6 +13714,11 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + /ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + dev: false + /iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} dev: false @@ -14091,7 +14231,7 @@ packages: '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color dev: true @@ -14528,7 +14668,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.7.1 transitivePeerDependencies: - supports-color dev: true @@ -14668,6 +14808,12 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /json-schema-ref-resolver@2.0.1: + resolution: {integrity: sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==} + dependencies: + dequal: 2.0.3 + dev: false + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -14958,6 +15104,14 @@ packages: type-check: 0.4.0 dev: true + /light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + dependencies: + cookie: 1.0.2 + process-warning: 4.0.1 + set-cookie-parser: 2.7.1 + dev: false + /lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -16706,7 +16860,7 @@ packages: dependencies: hosted-git-info: 6.1.1 is-core-module: 2.13.0 - semver: 7.6.3 + semver: 7.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -16733,7 +16887,7 @@ packages: resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.3 + semver: 7.7.1 dev: true /npm-normalize-package-bin@3.0.1: @@ -16747,7 +16901,7 @@ packages: dependencies: hosted-git-info: 6.1.1 proc-log: 3.0.0 - semver: 7.6.3 + semver: 7.7.1 validate-npm-package-name: 5.0.0 dev: true @@ -16758,7 +16912,7 @@ packages: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 - semver: 7.6.3 + semver: 7.7.1 dev: true /npm-run-all2@6.2.6: @@ -16894,6 +17048,9 @@ packages: knex: 3.1.0(pg@8.11.3) dev: false + /obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + /oer-utils@5.1.3-alpha.2: resolution: {integrity: sha512-BX/8xcb+TXGTRtu97a/ZvcrWSyMd8wt8GSQMD7MvKxPEFqN4lX9w+Pk/Wm4HxwfHQQO9EhTYryR5hosTgWxP+A==} dependencies: @@ -17385,7 +17542,10 @@ packages: /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - dev: false + + /pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} /pg-pool@3.6.1(pg@8.11.3): resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} @@ -17397,7 +17557,6 @@ packages: /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -17410,6 +17569,18 @@ packages: postgres-interval: 1.2.0 dev: false + /pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.4 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + /pg@8.11.3: resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} engines: {node: '>= 8.0.0'} @@ -17465,6 +17636,12 @@ packages: readable-stream: 4.1.0 split2: 4.1.0 + /pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + dependencies: + split2: 4.1.0 + dev: false + /pino-pretty@11.0.0: resolution: {integrity: sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==} hasBin: true @@ -17487,6 +17664,10 @@ packages: /pino-std-serializers@6.0.0: resolution: {integrity: sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==} + /pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + dev: false + /pino@8.19.0: resolution: {integrity: sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==} hasBin: true @@ -17503,6 +17684,23 @@ packages: sonic-boom: 3.7.0 thread-stream: 2.1.0 + /pino@9.6.0: + resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 4.0.1 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.3.1 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + dev: false + /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -17716,16 +17914,30 @@ packages: engines: {node: '>=4'} dev: false + /postgres-array@3.0.4: + resolution: {integrity: sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==} + engines: {node: '>=12'} + /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} dev: false + /postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + dependencies: + obuf: 1.1.2 + /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} dev: false + /postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} @@ -17733,6 +17945,13 @@ packages: xtend: 4.0.2 dev: false + /postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + /postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -17802,6 +18021,14 @@ packages: /process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + /process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + dev: false + + /process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + dev: false + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -18609,6 +18836,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -18651,6 +18882,11 @@ packages: signal-exit: 3.0.7 dev: true + /ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + dev: false + /retext-latin@4.0.0: resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} dependencies: @@ -18702,6 +18938,10 @@ packages: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: false + /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -18869,6 +19109,12 @@ packages: is-regex: 1.2.1 dev: true + /safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + dependencies: + ret: 0.5.0 + dev: false + /safe-stable-stringify@2.3.1: resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} engines: {node: '>=10'} @@ -18915,6 +19161,10 @@ packages: /secure-json-parse@2.5.0: resolution: {integrity: sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w==} + /secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + dev: false + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -18943,7 +19193,6 @@ packages: resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true - dev: false /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} @@ -19281,6 +19530,12 @@ packages: dependencies: atomic-sleep: 1.0.0 + /sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -19979,6 +20234,12 @@ packages: dependencies: real-require: 0.2.0 + /thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + dependencies: + real-require: 0.2.0 + dev: false + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -20058,6 +20319,11 @@ packages: dependencies: is-number: 7.0.0 + /toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -20248,6 +20514,17 @@ packages: typescript: 5.8.3 dev: true + /tsx@4.19.4: + resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /turbo-stream@2.4.0: resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} @@ -21149,6 +21426,7 @@ packages: yaml: 2.7.0 optionalDependencies: fsevents: 2.3.3 + dev: true /vite@6.2.5(@types/node@20.12.7)(yaml@2.7.0): resolution: {integrity: sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==} @@ -21197,7 +21475,6 @@ packages: yaml: 2.7.0 optionalDependencies: fsevents: 2.3.3 - dev: true /vitefu@1.0.6(vite@6.2.5): resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} @@ -21207,7 +21484,7 @@ packages: vite: optional: true dependencies: - vite: 6.2.5(@types/node@18.11.9)(yaml@2.7.0) + vite: 6.2.5(@types/node@20.12.7)(yaml@2.7.0) dev: false /vscode-jsonrpc@8.2.0: diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md new file mode 100644 index 0000000000..76b9722007 --- /dev/null +++ b/test/hsm-emulator/README.md @@ -0,0 +1,41 @@ +# HSM Emulator: + +The `hsm-emulator` is a lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for +development and testing purposes. + +## Overview + +The HSM Emulator provides cryptographic operations such as key management between card and processor parties, enabling +developers to validate HSM-integrated workflows without requiring access to a physical HSM. + +## Parties + +The parties involved in the HSM emulator include: + +- **ILF** - The ILF will be the activing ASE (Accont Serving Entity) +- **KaiOS** - The POS manufacturer +- **Austria Card** - The card issuer + +## Build HSM Emulator: + +The project is built as part of the Rafiki project. + +## Start HSM Emulator + +```shell +# Run (port 5002 default): +pnpm dev +``` + +## wdsd + +```mermaid +--- +title: Order example +--- +erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + CUSTOMER }|..|{ DELIVERY-ADDRESS : uses + +``` diff --git a/test/hsm-emulator/hsm.http b/test/hsm-emulator/hsm.http new file mode 100644 index 0000000000..786093813d --- /dev/null +++ b/test/hsm-emulator/hsm.http @@ -0,0 +1,101 @@ +### 1. HSM-ILF: Generate ZMK +POST http://localhost:5002/hsm-ilf/generate-zmk +Content-Type: application/json + +{} + + +> {% + client.global.set("zmk_component1", response.body.component1); + client.global.set("zmk_component2", response.body.component2); + client.global.set("zmk_component3", response.body.component3); + client.global.set("zmk_kcv", response.body.kcv); + client.global.set("zmk_tr31_lmk_ilf", response.body.tr31Block); +%} + + +### 2. Import the ZMK @ KaiOS: +POST http://localhost:5002/hsm-kai/import-zmk +content-type: application/json + +{ + "component1": "{{zmk_component1}}", + "component2": "{{zmk_component2}}", + "component3": "{{zmk_component3}}", + "kcv": "{{zmk_kcv}}" +} + + +> {% + client.global.set("zmk_tr31_lmk_kai", response.body.tr31Block); +%} + + +### 3. Import the ZMK @ Austria Card: +POST http://localhost:5002/hsm-austria-card/import-zmk +content-type: application/json + +{ + "component1": "{{zmk_component1}}", + "component2": "{{zmk_component2}}", + "component3": "{{zmk_component3}}", + "kcv": "{{zmk_kcv}}" +} + + +> {% + client.global.set("zmk_tr31_lmk_austria_card", response.body.tr31Block); +%} + + +### 4. Generate a TMK @ KaiOS, export under ZMK: +POST http://localhost:5002/hsm-kai/generate-tmk +content-type: application/json + +{ + "terminalSerial": "KAISN0000111", + "zmkUnderLmk": "{{zmk_tr31_lmk_kai}}" +} + + +> {% + client.global.set("tmk_tr31_lmk_kai", response.body.tr31TmkUnderLmk); + client.global.set("tmk_tr31_zmk", response.body.tr31TmkUnderZmk); + client.global.set("tmk_serial", response.body.terminalSerial); + client.global.set("tmk_kcv", response.body.kcv); +%} + + +### 5. Import the generated TMK @ ILF under ZMK: +### At this point, the terminal has the TMK, so we can transport keys to the device/terminal securely (once TMK imported). +POST http://localhost:5002/hsm-ilf/import-tmk +content-type: application/json + +{ + "tr31TmkUnderZmk": "{{tmk_tr31_zmk}}", + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}", + "kcv": "{{tmk_kcv}}" +} + + +> {% + client.global.set("tmk_tr31_lmk_ilf", response.body.tr31TmkUnderLmk); +%} + + +### 6. Generate a Card KeyPair @ ILF, export under ZMK: +POST http://localhost:5002/hsm-ilf/generate-card-key +content-type: application/json + +{ + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}" +} + + +> {% + client.global.set("cardkey_tr31_lmk_ilf", response.body.tr31CardKeyUnderLmk); + client.global.set("cardkey_tr31_zmk", response.body.tr31CardKeyUnderZmk); + client.global.set("cardkey_public", response.body.publicKey); + client.global.set("cardkey_kcv", response.body.kcv); +%} + diff --git a/test/hsm-emulator/package.json b/test/hsm-emulator/package.json new file mode 100644 index 0000000000..ee7f7073bb --- /dev/null +++ b/test/hsm-emulator/package.json @@ -0,0 +1,25 @@ +{ + "name": "hsm-emulator", + "description": "A lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for development and testing purposes. It provides cryptographic operations such as key management, digital signing, and verification over HTTP, enabling developers to validate HSM-integrated workflows without requiring access to a physical HSM.", + "private": true, + "version": "1.0.0", + "scripts": { + "dev": "tsx src/index.ts", + "test": "node --import tsx --test test/*.test.ts", + "build": "pnpm clean && tsc --build tsconfig.json", + "clean": "rm -rf dist" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "fastify": "^5.2.1", + "pino": "^9.6.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/pg": "^8.11.11", + "typescript": "^5.0.0", + "tsx": "^4.19.3" + } +} diff --git a/test/hsm-emulator/src/app.ts b/test/hsm-emulator/src/app.ts new file mode 100644 index 0000000000..c9bc57d4d7 --- /dev/null +++ b/test/hsm-emulator/src/app.ts @@ -0,0 +1,181 @@ +import fastify from 'fastify' +import logger from './logger' +import { + AES_AUSTRIA_CARD_LMK_HEX, + AES_ILF_LMK_HEX, + AES_KAI_LMK_HEX, + createTR31KeyBlockUnder, + importTMK, + generate3DESKeyFromComponents, + generateTMK, + import3DESKeyFromComponents, + KeyUsage, + Tr31Intent, + generateCardKey +} from './card-hsm' + +export function createApp(port: number) { + const app = fastify() + + app.post('/hsm-ilf/generate-zmk', async function handler(ffReq, ffReply) { + const genCleanZmkKey = generate3DESKeyFromComponents() + const { iv, tr31Block } = createTR31KeyBlockUnder( + Tr31Intent.LMK, + AES_ILF_LMK_HEX, + KeyUsage.ZMK, + 'T', + genCleanZmkKey.finalKeyBuffer + ) + + logger.info( + `Generated ZMK: ${tr31Block.toString('ascii')} with KCV: ${genCleanZmkKey.kcv}` + ) + + ffReply.code(200).send({ + iv: iv.toString('hex'), + tr31Block: tr31Block.toString('ascii'), // Only to be used with ILF HSM + component1: genCleanZmkKey.component1, // DANGER! Sent to custodian 1. + component2: genCleanZmkKey.component2, // DANGER! Sent to custodian 2. + component3: genCleanZmkKey.component3, // DANGER! Sent to custodian 3. + finalKey: genCleanZmkKey.finalKey, // FATAL! Never in the clear. XOR of key elements. + kcv: genCleanZmkKey.kcv // This allows all parties to verify the integrity of the key. + }) + }) + + app.post('/hsm-kai/import-zmk', async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { component1, component2, component3, kcv } = requestBody + + const zmkKey = import3DESKeyFromComponents( + AES_KAI_LMK_HEX, + KeyUsage.ZMK, + component1, + component2, + component3, + kcv + ) + + logger.info( + `KaiOS imported ZMK '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` + ) + + ffReply.code(200).send({ + iv: zmkKey.iv.toString('hex'), + tr31Block: zmkKey.tr31KeyBlock, // Only to be used with KAI HSM + kcv // This allows all parties to verify the integrity of the key. + }) + }) + + app.post( + '/hsm-austria-card/import-zmk', + async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { component1, component2, component3, kcv } = requestBody + + const zmkKey = import3DESKeyFromComponents( + AES_AUSTRIA_CARD_LMK_HEX, + KeyUsage.ZMK, + component1, + component2, + component3, + kcv + ) + + logger.info( + `AustriaCard imported ZMK '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` + ) + + ffReply.code(200).send({ + iv: zmkKey.iv.toString('hex'), + tr31Block: zmkKey.tr31KeyBlock, // Only to be used with KAI HSM + kcv // This allows all parties to verify the integrity of the key. + }) + } + ) + + app.post('/hsm-kai/generate-tmk', async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { zmkUnderLmk, terminalSerial } = requestBody + + const genTmkKey = generateTMK(AES_KAI_LMK_HEX, zmkUnderLmk) + + logger.info( + `KaiOS generated TMK '${genTmkKey.tr31TmkUnderLmk}|${genTmkKey.tr31TmkUnderZmk}' with KCV: ${genTmkKey.kcv}` + ) + + ffReply.code(200).send({ + tr31TmkUnderLmk: genTmkKey.tr31TmkUnderLmk, + tr31TmkUnderZmk: genTmkKey.tr31TmkUnderZmk, + terminalSerial, + kcv: genTmkKey.kcv + }) + }) + + app.post('/hsm-ilf/import-tmk', async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31ZmkUnderLmk, tr31TmkUnderZmk, kcv } = requestBody + + const tr31TmkUnderLmk = importTMK( + AES_ILF_LMK_HEX, + tr31ZmkUnderLmk, + tr31TmkUnderZmk, + kcv + ) + + logger.info( + `ILF imported TMK '${tr31TmkUnderLmk.tr31TmkUnderLmk}' with KCV: ${tr31TmkUnderLmk.kcv}` + ) + + ffReply.code(200).send({ + tr31TmkUnderLmk: tr31TmkUnderLmk.tr31TmkUnderLmk, + kcv: tr31TmkUnderLmk.kcv + }) + }) + + // At this point, we have a mechanism for transporting keys to the card and terminal via TMK. + // We are now able to issue SRED and PIN BDK keys. + + app.post( + '/hsm-ilf/generate-card-key', + async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31ZmkUnderLmk } = requestBody + const genCardKey = generateCardKey(AES_ILF_LMK_HEX, tr31ZmkUnderLmk) + + logger.info( + `ILF generated Card Key-Pair '${genCardKey.tr31CardKeyUnderLmk}|${genCardKey.tr31CardKeyUnderZmk}' with KCV: ${genCardKey.kcv}` + ) + + ffReply.code(200).send({ + tr31CardKeyUnderLmk: genCardKey.tr31CardKeyUnderLmk, + tr31CardKeyUnderZmk: genCardKey.tr31CardKeyUnderZmk, + publicKey: genCardKey.publicKey, + kcv: genCardKey.kcv + }) + } + ) + + app.post( + '/hsm-kai-os/generate-and-export-tmk', + async function handler(ffReq, ffReply) { + /*const genClearTMK = generate3DESKeyFromComponents() + + const zmkUnderLmk = 'TR31' + const zmkClear = + + const { iv, tr31Block } = createTR31KeyBlock(AES_LMK_HEX, 'ZMK', 'T', genCleanZmkKey.finalKeyBuffer); + */ + //TODO 2. Encrypt the final ZMK key under LMK + ffReply.code(200).send({ + signatureVerified: true + }) + } + ) + + return async () => { + await app.listen({ port, host: '0.0.0.0' }) + logger.info( + `πŸ—ƒ -> πŸ”‘ <-πŸ—ƒ 'Rafiki-HSM-Emulator' Listening on port '${port}'` + ) + } +} diff --git a/test/hsm-emulator/src/card-hsm.ts b/test/hsm-emulator/src/card-hsm.ts new file mode 100644 index 0000000000..c9c8bad658 --- /dev/null +++ b/test/hsm-emulator/src/card-hsm.ts @@ -0,0 +1,459 @@ +import { randomBytes, createCipheriv, createDecipheriv } from 'crypto' +import logger from './logger' +import { generateKeyPairSync } from 'node:crypto' + +/** + * The AES Local Master Key for the ILF HSM. + */ +const AES_ILF_LMK_HEX = + '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' +/** + * The AES Local Master Key for the KaiOS HSM. + */ +const AES_KAI_LMK_HEX = + 'ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100' +/** + * The AES Local Master Key for the Austria Card HSM. + */ +const AES_AUSTRIA_CARD_LMK_HEX = + 'eeeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433222211' + +enum KeyUsage { + ZMK, + TMK, + DEK, + BDK +} + +enum Tr31Intent { + LMK, + ZMK +} + +// XOR two buffers +function xorBuffers(buf1: Buffer, buf2: Buffer): Buffer { + if (buf1.length !== buf2.length) { + throw new Error('Buffers must be the same length for XOR') + } + const result = Buffer.alloc(buf1.length) + for (let i = 0; i < buf1.length; i++) { + result[i] = buf1[i] ^ buf2[i] + } + return result +} + +// Generate 3DES key from 3 components (each 24 bytes) +function generate3DESKeyFromComponents(): { + component1: string + component2: string + component3: string + finalKey: string + finalKeyBuffer: Buffer + kcv: string +} { + const length = 24 // 3DES key length (3 x 8 bytes = 24 bytes) + + const keyComponent1 = randomBytes(length) + const keyComponent2 = randomBytes(length) + const keyComponent3 = randomBytes(length) + + // Combine via XOR + const tempXor = xorBuffers(keyComponent1, keyComponent2) + const finalKey = xorBuffers(tempXor, keyComponent3) + + logger.info('Key Component 1:', keyComponent1.toString('hex')) + logger.info('Key Component 2:', keyComponent2.toString('hex')) + logger.info('Key Component 3:', keyComponent3.toString('hex')) + logger.info('Final 3DES Key :', finalKey.toString('hex')) + + return { + component1: keyComponent1.toString('hex').toUpperCase(), + component2: keyComponent2.toString('hex').toUpperCase(), + component3: keyComponent3.toString('hex').toUpperCase(), + finalKey: finalKey.toString('hex').toUpperCase(), + finalKeyBuffer: finalKey, + kcv: obtainKCVFrom3DESKey(finalKey) + } +} + +function import3DESKeyFromComponents( + kekHex: string, + keyUsage: KeyUsage, + component1: string, + component2: string, + component3: string, + kcv?: string +): { + finalKey: string + finalKeyBuffer: Buffer + tr31KeyBlock: string + iv: Buffer + kcv: string +} { + const keyComponent1 = Buffer.from(component1, 'hex') + const keyComponent2 = Buffer.from(component2, 'hex') + const keyComponent3 = Buffer.from(component3, 'hex') + + // Combine via XOR + const tempXor = xorBuffers(keyComponent1, keyComponent2) + const finalKey = xorBuffers(tempXor, keyComponent3) + const finalKeyKcv = obtainKCVFrom3DESKey(finalKey) + if (kcv && kcv !== finalKeyKcv) { + throw new Error(`Expected KCV '${kcv}' but got '${finalKeyKcv}' instead.`) + } + + const { iv, tr31Block } = createTR31KeyBlockUnder( + Tr31Intent.LMK, + kekHex, + keyUsage, + 'T', //T for TDEA, A for AES + finalKey + ) + + return { + finalKey: finalKey.toString('hex').toUpperCase(), + finalKeyBuffer: finalKey, + tr31KeyBlock: tr31Block.toString('ascii').toUpperCase(), + iv, + kcv: finalKeyKcv + } +} + +function generateTMK( + lmk: string, + tr31ZmkUnderLmk: string +): { + tr31TmkUnderLmk: string + tr31TmkUnderZmk: string + kcv: string +} { + const { clearKeyHex } = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmk, + tr31ZmkUnderLmk + ) + + const tmkRaw = randomBytes(24) + const tmkKCV = obtainKCVFrom3DESKey(tmkRaw) + + const tr31TmkUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmk, + KeyUsage.TMK, + 'T', + tmkRaw + ) + const tr31TmkUnderZmk = createTR31KeyBlockUnder( + Tr31Intent.ZMK, + clearKeyHex, //ZMK + KeyUsage.TMK, + 'T', + tmkRaw + ) + + return { + tr31TmkUnderLmk: tr31TmkUnderLmk.tr31Block.toString('ascii'), + tr31TmkUnderZmk: tr31TmkUnderZmk.tr31Block.toString('ascii'), + kcv: tmkKCV + } +} + +function importTMK( + lmkHex: string, + tr31ZmkUnderLmk: string, + tr31TmkUnderZmk: string, + kcv?: string +): { + tr31TmkUnderLmk: string + kcv: string +} { + // 1. Obtain the clear ZMK key from the ZMK under LMK: + const clearZmkKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmkHex, + tr31ZmkUnderLmk + ) + + // 2. Obtain the clear TMK key: + const clearTmkKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.ZMK, + clearZmkKey.clearKeyHex, + tr31TmkUnderZmk + ) + if (kcv && kcv !== clearTmkKey.kcv) { + throw new Error( + `Expected KCV '${kcv}' but got '${clearTmkKey.kcv}' instead.` + ) + } + + const tmkUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmkHex, + KeyUsage.TMK, + 'T', + clearTmkKey.clearKey + ) + + return { + kcv: clearTmkKey.kcv, + tr31TmkUnderLmk: tmkUnderLmk.tr31Block.toString('ascii') + } +} + +function generateCardKey( + lmk: string, + tr31ZmkUnderLmk: string, + keySize: number = 2048, + passphrase: string = 'your-secure-passphrase' +): { + tr31CardKeyUnderLmk: string + tr31CardKeyUnderZmk: string + publicKey: string + kcv: string +} { + const { publicKey, privateKey } = generateKeyPairSync('rsa', { + modulusLength: keySize, // Key size in bits + publicKeyEncoding: { + type: 'spki', // Recommended for public keys + format: 'pem' //pem for string, der for buffer. + }, + privateKeyEncoding: { + type: 'pkcs8', // Recommended for private keys + format: 'der', + cipher: 'aes-256-cbc', // Optional encryption + passphrase // Optional passphrase + } + }) + + logger.info(`Private key size is ${privateKey.length} bytes.`) + + const tr31CardKeyUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmk, + KeyUsage.DEK, + 'T', + privateKey + ) + + const { clearKeyHex } = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmk, + tr31ZmkUnderLmk + ) + const tr31CardKeyUnderZmk = createTR31KeyBlockUnder( + Tr31Intent.ZMK, + clearKeyHex, //ZMK + KeyUsage.DEK, + 'T', + privateKey + ) + + //TODO Test: + const pvtKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmk, + tr31CardKeyUnderLmk.tr31Block.toString('ascii') + ) + + logger.info(`Private key [BACK] size is ${pvtKey.clearKey.length} bytes.`) + + return { + tr31CardKeyUnderLmk: tr31CardKeyUnderLmk.tr31Block.toString('ascii'), + tr31CardKeyUnderZmk: tr31CardKeyUnderZmk.tr31Block.toString('ascii'), + publicKey, + kcv: 'XXXXXX' + } +} + +function obtainKCVFrom3DESKey(key: Buffer): string { + const data = Buffer.alloc(8, 0x00) // 8 bytes of zeros + const cipher = createCipheriv('des-ede3', key, null) + const encrypted = Buffer.concat([cipher.update(data), cipher.final()]) + return encrypted.slice(0, 3).toString('hex').toUpperCase() // First 3 bytes +} + +function encryptWithAES256( + plaintext: Buffer, + aesKeyHex: string, + zeroIv: boolean = true +): { iv: Buffer; ciphertext: Buffer; ciphertextHex: string } { + if (aesKeyHex.length !== 64) + throw new Error('AES key must be 64 hex characters (256 bits / 32 bytes)') + + const key = Buffer.from(aesKeyHex, 'hex') + const iv = zeroIv ? Buffer.alloc(16) : randomBytes(16) // AES block size = 16 bytes + + const cipher = createCipheriv('aes-256-cbc', key, iv) + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]) + + return { + iv, + ciphertext: encrypted, + ciphertextHex: encrypted.toString('hex') + } +} + +function encryptWith3DES( + plaintext: Buffer, + keyHex: string, + zeroIv: boolean = true +): { iv: Buffer; ciphertext: Buffer; ciphertextHex: string } { + if (keyHex.length !== 48) + throw new Error('3DES key must be 48 hex characters (24 bytes)') + + const key = Buffer.from(keyHex, 'hex') + const iv = zeroIv ? Buffer.alloc(8) : randomBytes(8) // 3DES block size = 8 bytes + + const cipher = createCipheriv('des-ede3-cbc', key, iv) + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]) + + return { + iv, + ciphertext: encrypted, + ciphertextHex: encrypted.toString('hex') + } +} + +function decryptWithAES256( + ciphertext: Buffer, + aesKeyHex: string, + iv: Buffer +): Buffer { + if (aesKeyHex.length !== 64) + throw new Error('AES key must be 64 hex characters (256 bits / 32 bytes)') + if (iv.length !== 16) throw new Error('IV must be 16 bytes (128 bits)') + + const key = Buffer.from(aesKeyHex, 'hex') + const decipher = createDecipheriv('aes-256-cbc', key, iv) + const decrypted = Buffer.concat([ + decipher.update(ciphertext), + decipher.final() + ]) + return decrypted +} + +function decryptWith3DES( + ciphertext: Buffer, + keyHex: string, + iv: Buffer +): Buffer { + if (keyHex.length !== 48) + throw new Error('3DES key must be 48 hex characters (24 bytes)') + if (iv.length !== 8) throw new Error('IV must be 8 bytes (64 bits)') + + const key = Buffer.from(keyHex, 'hex') + const decipher = createDecipheriv('des-ede3-cbc', key, iv) + return Buffer.concat([decipher.update(ciphertext), decipher.final()]) +} + +function createTR31KeyBlockUnder( + intent: Tr31Intent, + kekHex: string, // 32-byte AES key (hex-encoded) + keyUsage: KeyUsage, // 3 chars, e.g., 'EK' for Encryption Key + keyType: string, // 1 char, e.g., 'T' for TDEA, 'A' for AES + key: Buffer, // Key material (e.g., 24 bytes for 3DES) + zeroIv: boolean = true +): { iv: Buffer; tr31Block: Buffer } { + if (intent == Tr31Intent.LMK && kekHex.length !== 64) + throw new Error('KEK (LMK) must be 64 hex chars (32 bytes)') + else if (intent == Tr31Intent.ZMK && kekHex.length !== 48) + throw new Error('KEK (ZMK) must be 48 hex chars (24 bytes)') + + // for data, we convert to ASCII-HEX: + if (keyUsage === KeyUsage.DEK) key = Buffer.from(key.toString('hex')) + + // Encrypt using AES-256-CBC + const encryptedKey = + intent == Tr31Intent.LMK + ? encryptWithAES256(key, kekHex, zeroIv) + : encryptWith3DES(key, kekHex, zeroIv) + + const encryptedKeyHex = encryptedKey.ciphertextHex.toUpperCase() + + // TR-31 Header – 16 bytes (simplified) + const version = intent == Tr31Intent.LMK ? 'S' : 'B' // 1 byte: Version ID + const reserved = '0' // 1 byte: Reserved + const usage = `${KeyUsage[keyUsage]}` // 3 bytes: Key Usage (e.g., 'ZMK' / 'DEK') + const algo = keyType // 1 byte: Key Algorithm ID ('T' for 3DES) + const exportFlag = 'N' // 1 byte: Export flag + const numComponents = '1' // 1 byte: Key components (assume 1) + const generationMethod = 'K' // 1 byte: Key generation method + const length = encryptedKeyHex.length.toString().padStart(4, '0') // 4 bytes: Key length + const reserved2 = '000000000' // 9 bytes reserved + + const headerStr = + version + + reserved + + usage + + algo + + exportFlag + + numComponents + + generationMethod + + length + + reserved2 + const header = Buffer.from(headerStr, 'ascii') // total 16 bytes + const kcv = keyUsage === KeyUsage.DEK ? 'XXXXXX' : obtainKCVFrom3DESKey(key) + + // Combine header and key + const tr31Block = Buffer.concat([ + header, + Buffer.from(encryptedKeyHex), + Buffer.from(kcv) + ]) + + return { + iv: encryptedKey.iv, + tr31Block + } +} + +function extractClearKeyFromTR31KeyBlock( + intent: Tr31Intent, + kekHex: string, + tr31: string +): { + clearKey: Buffer + clearKeyHex: string + kcv: string +} { + const keyLength = parseInt(tr31.substring(9, 13)) + const usage = KeyUsage[tr31.substring(2, 5) as keyof typeof KeyUsage] + const key = tr31.substring(22, 22 + keyLength) + const tr31EncKeyPortion = Buffer.from(key, 'hex') + let clearKey = + intent === Tr31Intent.LMK + ? decryptWithAES256(tr31EncKeyPortion, kekHex, Buffer.alloc(16)) + : decryptWith3DES(tr31EncKeyPortion, kekHex, Buffer.alloc(8)) + if (usage == KeyUsage.DEK) + clearKey = Buffer.from(clearKey.toString('ascii'), 'hex') + + const tr31Kcv = tr31.slice(-6) + const kcvComputed = + usage == KeyUsage.DEK ? 'XXXXXX' : obtainKCVFrom3DESKey(clearKey) + if (kcvComputed !== tr31Kcv) { + throw new Error( + `Expected KCV '${tr31Kcv}' but got '${kcvComputed}'. Please confirm correct KEK is used.` + ) + } + + return { + clearKey: clearKey, + clearKeyHex: clearKey.toString('hex'), + kcv: kcvComputed + } +} + +export { + generate3DESKeyFromComponents, + import3DESKeyFromComponents, + obtainKCVFrom3DESKey, + createTR31KeyBlockUnder, + generateTMK, + importTMK, + generateCardKey, + AES_ILF_LMK_HEX, + AES_KAI_LMK_HEX, + AES_AUSTRIA_CARD_LMK_HEX, + KeyUsage, + Tr31Intent +} diff --git a/test/hsm-emulator/src/index.ts b/test/hsm-emulator/src/index.ts new file mode 100644 index 0000000000..dd22df10f0 --- /dev/null +++ b/test/hsm-emulator/src/index.ts @@ -0,0 +1,13 @@ +import { createApp } from './app' +import logger from './logger' +;(async () => { + const start = createApp( + Number(process.env['HTTP_HSM_EMULATOR_API_PORT'] ?? 5002) + ) + await start() + + process.on('SIGINT', () => { + logger.info('Received SIGINT. Shutting down gracefully...') + process.exit(0) + }) +})() diff --git a/test/hsm-emulator/src/logger.ts b/test/hsm-emulator/src/logger.ts new file mode 100644 index 0000000000..072d15c3b9 --- /dev/null +++ b/test/hsm-emulator/src/logger.ts @@ -0,0 +1,6 @@ +import pino from 'pino' + +export default pino({ + level: process.env['LOG_LEVEL'] || 'info', + base: null +}) diff --git a/test/hsm-emulator/tsconfig.json b/test/hsm-emulator/tsconfig.json new file mode 100644 index 0000000000..bc4252bfc2 --- /dev/null +++ b/test/hsm-emulator/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["**/*.test.ts", "src/test/*"] +} From 9dae0fcc6aed2c80c615482bea55875274af3b07 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 12:02:47 +0200 Subject: [PATCH 02/13] feat(car-6): readme updates. --- test/hsm-emulator/README.md | 50 +++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index 76b9722007..f7b80f142f 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -1,41 +1,69 @@ # HSM Emulator: - The `hsm-emulator` is a lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for development and testing purposes. ## Overview - The HSM Emulator provides cryptographic operations such as key management between card and processor parties, enabling developers to validate HSM-integrated workflows without requiring access to a physical HSM. ## Parties - The parties involved in the HSM emulator include: - - **ILF** - The ILF will be the activing ASE (Accont Serving Entity) - **KaiOS** - The POS manufacturer - **Austria Card** - The card issuer ## Build HSM Emulator: - The project is built as part of the Rafiki project. ## Start HSM Emulator - ```shell # Run (port 5002 default): pnpm dev ``` -## wdsd +## Key Management between Parties +The steps and digrams below illustrate the cryptographic keys and their relationship with one another and parties. + +### 1. ZMK - Zone Master Key +The ZMK may be generated by either of two or more parties. One party is responsible for a ZMK key generation, while the +other party is responsible for importing the ZMK. It is strongly advised that the ZMK be generated/imported using an HSM. +The KCV is used to verify integrity during the exchange. ```mermaid --- -title: Order example +title: ZMK Exchange --- erDiagram - CUSTOMER ||--o{ ORDER : places - ORDER ||--|{ LINE-ITEM : contains - CUSTOMER }|..|{ DELIVERY-ADDRESS : uses + "ILF 🏦" ||--}| "ZMK πŸ”‘" : generates + "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : imports + "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : imports +``` + +### 2. TMK - Terminal Master Key +The TMK is generated by the POS/terminal manufacturer. The TMK is securely transferred to the ASE, encrypted under the ZMK as TR-31 key-blocks. +```mermaid +--- +title: TMK Exchange +--- +erDiagram + "KaiOS πŸ“±" ||--}| "TMK πŸ”‘" : generates + "ILF 🏦" ||--}| "TMK πŸ”‘" : "imports (under ZMK)" ``` + +### 3. Card Key - Card Asymmetric Private Key + + +## Terms +Terms of definition related to ASE, Card issuer and terminal manufacturers. + +| Term | Description | +|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | +| LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | +| ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | +| TMK | Terminal Master Key. Used to secure the transmission of working keys (like PIN, SRED encryption keys or MAC keys) between a terminal and the HSM. | +| KCV | Key Check Value. Short cryptographic value derived from a key, used to verify that the key has been correctly transferred or entered without revealing the key itself. | +| POS | Point of Service device (also referred to as the terminal). | +| TR-31 | A TR-31 key block is a standardized format used to securely exchange cryptographic keys between systems, especially in financial environments involving HSMs (Hardware Security Modules). It was defined by the ANSI X9.24-1 standard. | + From 498f7cabc06e726f017c15ae84d8d9eea123a9a9 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 13:02:48 +0200 Subject: [PATCH 03/13] feat(car-6): card keys. --- test/hsm-emulator/README.md | 75 ++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index f7b80f142f..fb60945237 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -1,4 +1,4 @@ -# HSM Emulator: +# HSM Emulator The `hsm-emulator` is a lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for development and testing purposes. @@ -24,7 +24,7 @@ pnpm dev ## Key Management between Parties The steps and digrams below illustrate the cryptographic keys and their relationship with one another and parties. -### 1. ZMK - Zone Master Key +### 1.1. ZMK - Zone Master Key The ZMK may be generated by either of two or more parties. One party is responsible for a ZMK key generation, while the other party is responsible for importing the ZMK. It is strongly advised that the ZMK be generated/imported using an HSM. The KCV is used to verify integrity during the exchange. @@ -35,12 +35,16 @@ title: ZMK Exchange --- erDiagram "ILF 🏦" ||--}| "ZMK πŸ”‘" : generates - "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : imports - "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : imports + "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : imports (3x clear components) + "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : imports (3x clear components) ``` -### 2. TMK - Terminal Master Key -The TMK is generated by the POS/terminal manufacturer. The TMK is securely transferred to the ASE, encrypted under the ZMK as TR-31 key-blocks. +### 1.2. TMK - Terminal Master Key +The Terminal Master Key (TMK) is generated by the POS or terminal manufacturer and is unique to each terminal. +It is securely transferred to the Account Serving Entity (ASE), +encrypted under the Zone Master Key (ZMK) using the TR-31 key block format. +The TMK facilitates the secure delivery of session keys between the ASE and the terminal, +ensuring encrypted communication and key management. ```mermaid --- @@ -51,19 +55,56 @@ erDiagram "ILF 🏦" ||--}| "TMK πŸ”‘" : "imports (under ZMK)" ``` -### 3. Card Key - Card Asymmetric Private Key +### 1.3. PIN/SRED BDK and IPEK - Base Derivation Key and Initial PIN Encryption Keys +The Base Derivation Keys (BDKs) are generated by the Account Serving Entity (ASE) and typically remain consistent for each terminal +manufacturer or model. From the BDK, Initial PIN Encryption Keys (IPEKs) are derived and securely loaded onto the terminal, +encrypted under the Terminal Master Key (TMK) using the TR-31 key block format. +This process ensures secure delivery of keys to the terminal, either through direct injection or via an over-the-air Remote Key Injection (RKI) mechanism. +```mermaid +--- +title: PIN/SRED Key Exchange and IPEK +--- +erDiagram + "ILF 🏦" ||--|| "SRED/PIN BDK πŸ”‘" : "generates" + "SRED/PIN BDK πŸ”‘" ||--}| "IPEK πŸ”‘" : "ILF generates (based on BDK)" + "Terminal πŸ“±" ||--|| "IPEK πŸ”‘" : "imports (under TMK)" +``` + +### 2.1 Card Key - Card Asymmetric Keys +The Card Key Pair is generated by the Account Serving Entity (ASE), which also acts as the issuer. +A unique key pair is created for each card. +The private key from this pair is securely linked to a corresponding wallet address. + +```mermaid +--- +title: Card Key Generation and Issuing +--- +erDiagram + "ILF 🏦" ||--}| "Card KeyPair πŸ”" : "generates" + "Card KeyPair πŸ”" ||--}| "Private Key πŸ”‘" : "has" + "Card KeyPair πŸ”" ||--}| "Public Key πŸ”“" : "has" + "Public Key πŸ”“" |{--}| "Wallet Address πŸ“‡" : "ILF store against wallet address πŸ’½" + + "Austria Card πŸ’³" ||--}| "Private Key πŸ”‘" : "imports (under ZMK)" + + "Private Key πŸ”‘" ||--|| "Card πŸ’³" : "loaded onto (securely during issuing)" +``` ## Terms Terms of definition related to ASE, Card issuer and terminal manufacturers. -| Term | Description | -|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | -| LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | -| ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | -| TMK | Terminal Master Key. Used to secure the transmission of working keys (like PIN, SRED encryption keys or MAC keys) between a terminal and the HSM. | -| KCV | Key Check Value. Short cryptographic value derived from a key, used to verify that the key has been correctly transferred or entered without revealing the key itself. | -| POS | Point of Service device (also referred to as the terminal). | -| TR-31 | A TR-31 key block is a standardized format used to securely exchange cryptographic keys between systems, especially in financial environments involving HSMs (Hardware Security Modules). It was defined by the ANSI X9.24-1 standard. | - +| Term | Description | +|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | +| LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | +| ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | +| TMK | Terminal Master Key. Used to secure the transmission of working keys (like PIN, SRED encryption keys or MAC keys) between a terminal and the HSM. | +| BDK | Base Derivation Key, is the root key from which a unique IPEK (Initial PIN Encryption Key) is derived for each device or terminal. The IPEK, in turn, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | +| IPEK | Initial PIN Encryption Key, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | +| KSN | Key Serial Number. A unique identifier for each device, used in key derivation. | +| Session Key | Derived from the IPEK for encrypting a single transaction. | +| DUKPT | DUKPT stands for Derived Unique Key Per Transaction. It’s a key management scheme used primarily in payment systems (e.g. POS terminals) to ensure each transaction is encrypted with a unique key, dramatically reducing the risk of compromise. | +| KCV | Key Check Value. Short cryptographic value derived from a key, used to verify that the key has been correctly transferred or entered without revealing the key itself. | +| POS | Point of Service device (also referred to as the terminal). | +| TR-31 | A TR-31 key block is a standardized format used to securely exchange cryptographic keys between systems, especially in financial environments involving HSMs (Hardware Security Modules). It was defined by the ANSI X9.24-1 standard. | From 85306678742a5d616f82b9ca597d1228526490e6 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 13:04:34 +0200 Subject: [PATCH 04/13] feat(car-6): fix diagram. --- test/hsm-emulator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index fb60945237..ca5622578e 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -35,8 +35,8 @@ title: ZMK Exchange --- erDiagram "ILF 🏦" ||--}| "ZMK πŸ”‘" : generates - "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : imports (3x clear components) - "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : imports (3x clear components) + "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : "imports (3x clear components)" + "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : "imports (3x clear components)" ``` ### 1.2. TMK - Terminal Master Key From 990c7fbf716b626fbde49c957a23f74a6a241423 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 13:07:11 +0200 Subject: [PATCH 05/13] feat(car-6): fix typos. --- test/hsm-emulator/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index ca5622578e..baa02be428 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -67,7 +67,7 @@ title: PIN/SRED Key Exchange and IPEK --- erDiagram "ILF 🏦" ||--|| "SRED/PIN BDK πŸ”‘" : "generates" - "SRED/PIN BDK πŸ”‘" ||--}| "IPEK πŸ”‘" : "ILF generates (based on BDK)" + "SRED/PIN BDK πŸ”‘" ||--}| "IPEK πŸ”‘" : "generates (based on BDK)" "Terminal πŸ“±" ||--|| "IPEK πŸ”‘" : "imports (under TMK)" ``` @@ -91,7 +91,7 @@ erDiagram "Private Key πŸ”‘" ||--|| "Card πŸ’³" : "loaded onto (securely during issuing)" ``` -## Terms +## Glossary of Terms Terms of definition related to ASE, Card issuer and terminal manufacturers. | Term | Description | From 345309f1e633ea99023f3fc9c7f09a460e17fc67 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sun, 4 May 2025 13:14:57 +0200 Subject: [PATCH 06/13] feat(car-6): fix typos. --- test/hsm-emulator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index baa02be428..2b19534c2a 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -74,7 +74,7 @@ erDiagram ### 2.1 Card Key - Card Asymmetric Keys The Card Key Pair is generated by the Account Serving Entity (ASE), which also acts as the issuer. A unique key pair is created for each card. -The private key from this pair is securely linked to a corresponding wallet address. +The public key from this pair is securely linked to a corresponding wallet address. ```mermaid --- From 5d88277de3b6d4b1779a9f721b1e9d10a2daa727 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Thu, 8 May 2025 12:53:11 +0200 Subject: [PATCH 07/13] feat(car-6): use ec and update README.md --- test/hsm-emulator/README.md | 38 ++++++++++++++++++++----------- test/hsm-emulator/src/card-hsm.ts | 12 ++++++---- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index 2b19534c2a..a4c6fa016c 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -1,33 +1,41 @@ # HSM Emulator + The `hsm-emulator` is a lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for development and testing purposes. ## Overview + The HSM Emulator provides cryptographic operations such as key management between card and processor parties, enabling developers to validate HSM-integrated workflows without requiring access to a physical HSM. ## Parties + The parties involved in the HSM emulator include: + - **ILF** - The ILF will be the activing ASE (Accont Serving Entity) - **KaiOS** - The POS manufacturer - **Austria Card** - The card issuer ## Build HSM Emulator: + The project is built as part of the Rafiki project. ## Start HSM Emulator + ```shell # Run (port 5002 default): pnpm dev ``` ## Key Management between Parties + The steps and digrams below illustrate the cryptographic keys and their relationship with one another and parties. ### 1.1. ZMK - Zone Master Key -The ZMK may be generated by either of two or more parties. One party is responsible for a ZMK key generation, while the + +The ZMK may be generated by either of two or more parties. One party is responsible for a ZMK key generation, while the other party is responsible for importing the ZMK. It is strongly advised that the ZMK be generated/imported using an HSM. -The KCV is used to verify integrity during the exchange. +The KCV is used to verify integrity during the exchange. ```mermaid --- @@ -40,10 +48,11 @@ erDiagram ``` ### 1.2. TMK - Terminal Master Key -The Terminal Master Key (TMK) is generated by the POS or terminal manufacturer and is unique to each terminal. -It is securely transferred to the Account Serving Entity (ASE), -encrypted under the Zone Master Key (ZMK) using the TR-31 key block format. -The TMK facilitates the secure delivery of session keys between the ASE and the terminal, + +The Terminal Master Key (TMK) is generated by the POS or terminal manufacturer and is unique to each terminal. +It is securely transferred to the Account Serving Entity (ASE), +encrypted under the Zone Master Key (ZMK) using the TR-31 key block format. +The TMK facilitates the secure delivery of session keys between the ASE and the terminal, ensuring encrypted communication and key management. ```mermaid @@ -56,9 +65,10 @@ erDiagram ``` ### 1.3. PIN/SRED BDK and IPEK - Base Derivation Key and Initial PIN Encryption Keys -The Base Derivation Keys (BDKs) are generated by the Account Serving Entity (ASE) and typically remain consistent for each terminal -manufacturer or model. From the BDK, Initial PIN Encryption Keys (IPEKs) are derived and securely loaded onto the terminal, -encrypted under the Terminal Master Key (TMK) using the TR-31 key block format. + +The Base Derivation Keys (BDKs) are generated by the Account Serving Entity (ASE) and typically remain consistent for each terminal +manufacturer or model. From the BDK, Initial PIN Encryption Keys (IPEKs) are derived and securely loaded onto the terminal, +encrypted under the Terminal Master Key (TMK) using the TR-31 key block format. This process ensures secure delivery of keys to the terminal, either through direct injection or via an over-the-air Remote Key Injection (RKI) mechanism. ```mermaid @@ -72,8 +82,9 @@ erDiagram ``` ### 2.1 Card Key - Card Asymmetric Keys -The Card Key Pair is generated by the Account Serving Entity (ASE), which also acts as the issuer. -A unique key pair is created for each card. + +The Card Key Pair is generated by the Account Serving Entity (ASE), which also acts as the issuer. +A unique key pair is created for each card. The public key from this pair is securely linked to a corresponding wallet address. ```mermaid @@ -85,17 +96,18 @@ erDiagram "Card KeyPair πŸ”" ||--}| "Private Key πŸ”‘" : "has" "Card KeyPair πŸ”" ||--}| "Public Key πŸ”“" : "has" "Public Key πŸ”“" |{--}| "Wallet Address πŸ“‡" : "ILF store against wallet address πŸ’½" - + "Austria Card πŸ’³" ||--}| "Private Key πŸ”‘" : "imports (under ZMK)" "Private Key πŸ”‘" ||--|| "Card πŸ’³" : "loaded onto (securely during issuing)" ``` ## Glossary of Terms + Terms of definition related to ASE, Card issuer and terminal manufacturers. | Term | Description | -|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | | LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | | ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | diff --git a/test/hsm-emulator/src/card-hsm.ts b/test/hsm-emulator/src/card-hsm.ts index c9c8bad658..8ae1a9908c 100644 --- a/test/hsm-emulator/src/card-hsm.ts +++ b/test/hsm-emulator/src/card-hsm.ts @@ -203,7 +203,7 @@ function importTMK( function generateCardKey( lmk: string, tr31ZmkUnderLmk: string, - keySize: number = 2048, + //keySize: number = 2048, passphrase: string = 'your-secure-passphrase' ): { tr31CardKeyUnderLmk: string @@ -211,8 +211,10 @@ function generateCardKey( publicKey: string kcv: string } { - const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: keySize, // Key size in bits + // EllipticCurve: prime256v1 + const { publicKey, privateKey } = generateKeyPairSync('ec', { + //modulusLength: keySize, // Key size in bits (RSA/DSA) + namedCurve: 'prime256v1', publicKeyEncoding: { type: 'spki', // Recommended for public keys format: 'pem' //pem for string, der for buffer. @@ -225,7 +227,9 @@ function generateCardKey( } }) - logger.info(`Private key size is ${privateKey.length} bytes.`) + logger.info( + `Private key size is ${privateKey.length} bytes, and public key size is ${publicKey.length} bytes` + ) const tr31CardKeyUnderLmk = createTR31KeyBlockUnder( Tr31Intent.LMK, From 9d27ca999b2d5c4a57d4fb5ebd741317e6561199 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Wed, 14 May 2025 18:25:40 -0600 Subject: [PATCH 08/13] feat(car-6): import key completed. --- test/hsm-emulator/README.md | 2 +- test/hsm-emulator/hsm.http | 15 +++++++++ test/hsm-emulator/src/app.ts | 26 ++++++++++------ test/hsm-emulator/src/card-hsm.ts | 52 ++++++++++++++++++++++++++----- 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index a4c6fa016c..591e510ac4 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -107,7 +107,7 @@ erDiagram Terms of definition related to ASE, Card issuer and terminal manufacturers. | Term | Description | -| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | | LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | | ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | diff --git a/test/hsm-emulator/hsm.http b/test/hsm-emulator/hsm.http index 786093813d..00e0559f4f 100644 --- a/test/hsm-emulator/hsm.http +++ b/test/hsm-emulator/hsm.http @@ -99,3 +99,18 @@ content-type: application/json client.global.set("cardkey_kcv", response.body.kcv); %} + +### 7. import Card PrivateKey @ AustriaCard: +POST http://localhost:5002/hsm-austria-card/import-card-key +content-type: application/json + +{ + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_austria_card}}", + "tr31CardKeyUnderZmk": "{{cardkey_tr31_zmk}}", + "kcv": "{{cardkey_kcv}}" +} + + +> {% + client.global.set("cardkey_tr31_lmk_austria_card", response.body.tr31CardKeyUnderLmk); +%} diff --git a/test/hsm-emulator/src/app.ts b/test/hsm-emulator/src/app.ts index c9bc57d4d7..f71219887d 100644 --- a/test/hsm-emulator/src/app.ts +++ b/test/hsm-emulator/src/app.ts @@ -11,7 +11,8 @@ import { import3DESKeyFromComponents, KeyUsage, Tr31Intent, - generateCardKey + generateCardKey, + importCardKey } from './card-hsm' export function createApp(port: number) { @@ -156,18 +157,25 @@ export function createApp(port: number) { ) app.post( - '/hsm-kai-os/generate-and-export-tmk', + '/hsm-austria-card/import-card-key', async function handler(ffReq, ffReply) { - /*const genClearTMK = generate3DESKeyFromComponents() + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31ZmkUnderLmk, tr31CardKeyUnderZmk, kcv } = requestBody - const zmkUnderLmk = 'TR31' - const zmkClear = + const tr31CardKeyUnderLmk = importCardKey( + AES_AUSTRIA_CARD_LMK_HEX, + tr31ZmkUnderLmk, + tr31CardKeyUnderZmk, + kcv + ) + + logger.info( + `ILF imported CardKey '${tr31CardKeyUnderLmk.tr31CardKeyUnderLmk}' with KCV: ${tr31CardKeyUnderLmk.kcv}` + ) - const { iv, tr31Block } = createTR31KeyBlock(AES_LMK_HEX, 'ZMK', 'T', genCleanZmkKey.finalKeyBuffer); - */ - //TODO 2. Encrypt the final ZMK key under LMK ffReply.code(200).send({ - signatureVerified: true + tr31CardKeyUnderLmk: tr31CardKeyUnderLmk.tr31CardKeyUnderLmk, + kcv: tr31CardKeyUnderLmk.kcv }) } ) diff --git a/test/hsm-emulator/src/card-hsm.ts b/test/hsm-emulator/src/card-hsm.ts index 8ae1a9908c..311370d181 100644 --- a/test/hsm-emulator/src/card-hsm.ts +++ b/test/hsm-emulator/src/card-hsm.ts @@ -200,6 +200,48 @@ function importTMK( } } +function importCardKey( + lmkHex: string, + tr31ZmkUnderLmk: string, + tr31CardKeyUnderZmk: string, + kcv?: string +): { + tr31CardKeyUnderLmk: string + kcv: string +} { + // 1. Obtain the clear ZMK key from the ZMK under LMK: + const clearZmkKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmkHex, + tr31ZmkUnderLmk + ) + + // 2. Obtain the clear Card key: + const clearCardKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.ZMK, + clearZmkKey.clearKeyHex, + tr31CardKeyUnderZmk + ) + if (kcv && kcv !== clearCardKey.kcv) { + throw new Error( + `Expected KCV '${kcv}' but got '${clearCardKey.kcv}' instead.` + ) + } + + const cardKeyUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmkHex, + KeyUsage.DEK, + 'T', + clearCardKey.clearKey + ) + + return { + kcv: clearCardKey.kcv, + tr31CardKeyUnderLmk: cardKeyUnderLmk.tr31Block.toString('ascii') + } +} + function generateCardKey( lmk: string, tr31ZmkUnderLmk: string, @@ -252,14 +294,13 @@ function generateCardKey( privateKey ) - //TODO Test: const pvtKey = extractClearKeyFromTR31KeyBlock( Tr31Intent.LMK, lmk, tr31CardKeyUnderLmk.tr31Block.toString('ascii') ) - logger.info(`Private key [BACK] size is ${pvtKey.clearKey.length} bytes.`) + logger.debug(`Private key [BACK] size is ${pvtKey.clearKey.length} bytes.`) return { tr31CardKeyUnderLmk: tr31CardKeyUnderLmk.tr31Block.toString('ascii'), @@ -329,11 +370,7 @@ function decryptWithAES256( const key = Buffer.from(aesKeyHex, 'hex') const decipher = createDecipheriv('aes-256-cbc', key, iv) - const decrypted = Buffer.concat([ - decipher.update(ciphertext), - decipher.final() - ]) - return decrypted + return Buffer.concat([decipher.update(ciphertext), decipher.final()]) } function decryptWith3DES( @@ -455,6 +492,7 @@ export { generateTMK, importTMK, generateCardKey, + importCardKey, AES_ILF_LMK_HEX, AES_KAI_LMK_HEX, AES_AUSTRIA_CARD_LMK_HEX, From 72473a37e141e9632f9691ecf68df714d87a8b15 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Fri, 16 May 2025 17:11:00 -0600 Subject: [PATCH 09/13] feat(car-6): ipek key done. --- ...M AustriaCard - Import Card PrivateKey.bru | 36 +++ .../HSM AustriaCard - Import ZMK.bru | 37 +++ .../HSM Emulator/HSM ILF - Derive IPEK.bru | 39 +++ .../HSM Emulator/HSM ILF - Generate BDK.bru | 36 +++ .../HSM ILF - Generate Card Keypair.bru | 38 +++ .../HSM Emulator/HSM ILF - Generate ZMK.bru | 36 +++ .../HSM Emulator/HSM ILF - Import TMK.bru | 37 +++ .../HSM Emulator/HSM KaiOS - Generate TMK.bru | 38 +++ .../HSM Emulator/HSM KaiOS - Import ZMK.bru | 37 +++ .../Rafiki/environments/Local Playground.bru | 1 + test/hsm-emulator/README.md | 2 +- test/hsm-emulator/hsm.http | 116 --------- test/hsm-emulator/src/app.ts | 39 +++ test/hsm-emulator/src/card-hsm.ts | 237 +++++++++++++++--- 14 files changed, 578 insertions(+), 151 deletions(-) create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru create mode 100644 bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru delete mode 100644 test/hsm-emulator/hsm.http diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru b/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru new file mode 100644 index 0000000000..d0fe5528dc --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru @@ -0,0 +1,36 @@ +meta { + name: HSM AustriaCard - Import Card PrivateKey + type: http + seq: 9 +} + +post { + url: {{hsmEmulatorHost}}/hsm-austria-card/import-card-key + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_austria_card}}", + "tr31CardKeyUnderZmk": "{{cardkey_tr31_zmk}}", + "kcv": "{{cardkey_kcv}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31CardKeyUnderLmk) bru.setEnvVar("cardkey_tr31_lmk_austria_card", body.tr31CardKeyUnderLmk); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru new file mode 100644 index 0000000000..d1579b61c5 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru @@ -0,0 +1,37 @@ +meta { + name: HSM AustriaCard - Import ZMK + type: http + seq: 4 +} + +post { + url: {{hsmEmulatorHost}}/hsm-austria-card/import-zmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "component1": "{{zmk_component1}}", + "component2": "{{zmk_component2}}", + "component3": "{{zmk_component3}}", + "kcv": "{{zmk_kcv}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31Block) bru.setEnvVar("zmk_tr31_lmk_austria_card", body.tr31Block); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru new file mode 100644 index 0000000000..5c054bc82b --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru @@ -0,0 +1,39 @@ +meta { + name: HSM ILF - Derive IPEK + type: http + seq: 7 +} + +post { + url: {{hsmEmulatorHost}}/hsm-ilf/derive-ipek + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "tr31BdkUnderLmk": "{{bdk_tr31_lmk_ilf}}", + "tr31TmkUnderLmk": "{{tmk_tr31_lmk_ilf}}", + "ksnHex": "ffff9876543210e00000" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31IpekUnderLmk) bru.setEnvVar("ipek_tr31_lmk_ilf", body.tr31IpekUnderLmk); + if (body?.tr31IpekUnderTmk) bru.setEnvVar("ipek_tr31_tmk", body.tr31IpekUnderTmk); + if (body?.kcv) bru.setEnvVar("ipek_kcv", body.kcv); + + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru new file mode 100644 index 0000000000..d56c03d088 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru @@ -0,0 +1,36 @@ +meta { + name: HSM ILF - Generate BDK + type: http + seq: 2 +} + +post { + url: {{hsmEmulatorHost}}/hsm-ilf/generate-bdk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "zmkUnderLmk": "{{zmk_tr31_lmk_ilf}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31BdkUnderLmk) bru.setEnvVar("bdk_tr31_lmk_ilf", body.tr31BdkUnderLmk); + if (body?.tr31BdkUnderZmk) bru.setEnvVar("bdk_tr31_zmk", body.tr31BdkUnderZmk); + + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru new file mode 100644 index 0000000000..06b7326b25 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru @@ -0,0 +1,38 @@ +meta { + name: HSM ILF - Generate Card Keypair + type: http + seq: 8 +} + +post { + url: {{hsmEmulatorHost}}/hsm-ilf/generate-card-key + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31CardKeyUnderLmk) bru.setEnvVar("cardkey_tr31_lmk_ilf", body.tr31CardKeyUnderLmk); + if (body?.tr31CardKeyUnderZmk) bru.setEnvVar("cardkey_tr31_zmk", body.tr31CardKeyUnderZmk); + if (body?.publicKey) bru.setEnvVar("cardkey_public", body.publicKey); + if (body?.kcv) bru.setEnvVar("cardkey_kcv", body.kcv); + + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru new file mode 100644 index 0000000000..537c8290d6 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru @@ -0,0 +1,36 @@ +meta { + name: HSM ILF - Generate ZMK + type: http + seq: 1 +} + +post { + url: {{hsmEmulatorHost}}/hsm-ilf/generate-zmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + {} +} + +script:post-response { + const body = res.getBody(); + + if (body?.component1) bru.setEnvVar("zmk_component1", body.component1); + if (body?.component2) bru.setEnvVar("zmk_component2", body.component2); + if (body?.component3) bru.setEnvVar("zmk_component3", body.component3); + if (body?.kcv) bru.setEnvVar("zmk_kcv", body.kcv); + if (body?.tr31Block) bru.setEnvVar("zmk_tr31_lmk_ilf", body.tr31Block); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru new file mode 100644 index 0000000000..99038bf81c --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru @@ -0,0 +1,37 @@ +meta { + name: HSM ILF - Import TMK + type: http + seq: 6 +} + +post { + url: {{hsmEmulatorHost}}/hsm-ilf/import-tmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "tr31TmkUnderZmk": "{{tmk_tr31_zmk}}", + "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}", + "kcv": "{{tmk_kcv}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31TmkUnderLmk) bru.setEnvVar("tmk_tr31_lmk_ilf", body.tr31TmkUnderLmk); + + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru new file mode 100644 index 0000000000..d85009e9a8 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru @@ -0,0 +1,38 @@ +meta { + name: HSM KaiOS - Generate TMK + type: http + seq: 5 +} + +post { + url: {{hsmEmulatorHost}}/hsm-kai/generate-tmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "terminalSerial": "KAISN0000111", + "zmkUnderLmk": "{{zmk_tr31_lmk_kai}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31TmkUnderLmk) bru.setEnvVar("tmk_tr31_lmk_kai", body.tr31TmkUnderLmk); + if (body?.tr31TmkUnderZmk) bru.setEnvVar("tmk_tr31_zmk", body.tr31TmkUnderZmk); + if (body?.terminalSerial) bru.setEnvVar("tmk_serial", body.terminalSerial); + if (body?.kcv) bru.setEnvVar("tmk_kcv", body.kcv); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru new file mode 100644 index 0000000000..e34a5372e0 --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru @@ -0,0 +1,37 @@ +meta { + name: HSM KaiOS - Import ZMK + type: http + seq: 3 +} + +post { + url: {{hsmEmulatorHost}}/hsm-kai/import-zmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + { + "component1": "{{zmk_component1}}", + "component2": "{{zmk_component2}}", + "component3": "{{zmk_component3}}", + "kcv": "{{zmk_kcv}}" + } +} + +script:post-response { + const body = res.getBody(); + + if (body?.tr31Block) bru.setEnvVar("zmk_tr31_lmk_kai", body.tr31Block); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/environments/Local Playground.bru b/bruno/collections/Rafiki/environments/Local Playground.bru index 6cb1033ff6..406a7dfa25 100644 --- a/bruno/collections/Rafiki/environments/Local Playground.bru +++ b/bruno/collections/Rafiki/environments/Local Playground.bru @@ -30,4 +30,5 @@ vars { assetIdTigerBeetle: 1 assetCode: USD assetScale: 2 + hsmEmulatorHost: http://localhost:5002 } diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index 591e510ac4..a4c6fa016c 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -107,7 +107,7 @@ erDiagram Terms of definition related to ASE, Card issuer and terminal manufacturers. | Term | Description | -|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | | LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | | ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | diff --git a/test/hsm-emulator/hsm.http b/test/hsm-emulator/hsm.http deleted file mode 100644 index 00e0559f4f..0000000000 --- a/test/hsm-emulator/hsm.http +++ /dev/null @@ -1,116 +0,0 @@ -### 1. HSM-ILF: Generate ZMK -POST http://localhost:5002/hsm-ilf/generate-zmk -Content-Type: application/json - -{} - - -> {% - client.global.set("zmk_component1", response.body.component1); - client.global.set("zmk_component2", response.body.component2); - client.global.set("zmk_component3", response.body.component3); - client.global.set("zmk_kcv", response.body.kcv); - client.global.set("zmk_tr31_lmk_ilf", response.body.tr31Block); -%} - - -### 2. Import the ZMK @ KaiOS: -POST http://localhost:5002/hsm-kai/import-zmk -content-type: application/json - -{ - "component1": "{{zmk_component1}}", - "component2": "{{zmk_component2}}", - "component3": "{{zmk_component3}}", - "kcv": "{{zmk_kcv}}" -} - - -> {% - client.global.set("zmk_tr31_lmk_kai", response.body.tr31Block); -%} - - -### 3. Import the ZMK @ Austria Card: -POST http://localhost:5002/hsm-austria-card/import-zmk -content-type: application/json - -{ - "component1": "{{zmk_component1}}", - "component2": "{{zmk_component2}}", - "component3": "{{zmk_component3}}", - "kcv": "{{zmk_kcv}}" -} - - -> {% - client.global.set("zmk_tr31_lmk_austria_card", response.body.tr31Block); -%} - - -### 4. Generate a TMK @ KaiOS, export under ZMK: -POST http://localhost:5002/hsm-kai/generate-tmk -content-type: application/json - -{ - "terminalSerial": "KAISN0000111", - "zmkUnderLmk": "{{zmk_tr31_lmk_kai}}" -} - - -> {% - client.global.set("tmk_tr31_lmk_kai", response.body.tr31TmkUnderLmk); - client.global.set("tmk_tr31_zmk", response.body.tr31TmkUnderZmk); - client.global.set("tmk_serial", response.body.terminalSerial); - client.global.set("tmk_kcv", response.body.kcv); -%} - - -### 5. Import the generated TMK @ ILF under ZMK: -### At this point, the terminal has the TMK, so we can transport keys to the device/terminal securely (once TMK imported). -POST http://localhost:5002/hsm-ilf/import-tmk -content-type: application/json - -{ - "tr31TmkUnderZmk": "{{tmk_tr31_zmk}}", - "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}", - "kcv": "{{tmk_kcv}}" -} - - -> {% - client.global.set("tmk_tr31_lmk_ilf", response.body.tr31TmkUnderLmk); -%} - - -### 6. Generate a Card KeyPair @ ILF, export under ZMK: -POST http://localhost:5002/hsm-ilf/generate-card-key -content-type: application/json - -{ - "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_ilf}}" -} - - -> {% - client.global.set("cardkey_tr31_lmk_ilf", response.body.tr31CardKeyUnderLmk); - client.global.set("cardkey_tr31_zmk", response.body.tr31CardKeyUnderZmk); - client.global.set("cardkey_public", response.body.publicKey); - client.global.set("cardkey_kcv", response.body.kcv); -%} - - -### 7. import Card PrivateKey @ AustriaCard: -POST http://localhost:5002/hsm-austria-card/import-card-key -content-type: application/json - -{ - "tr31ZmkUnderLmk": "{{zmk_tr31_lmk_austria_card}}", - "tr31CardKeyUnderZmk": "{{cardkey_tr31_zmk}}", - "kcv": "{{cardkey_kcv}}" -} - - -> {% - client.global.set("cardkey_tr31_lmk_austria_card", response.body.tr31CardKeyUnderLmk); -%} diff --git a/test/hsm-emulator/src/app.ts b/test/hsm-emulator/src/app.ts index f71219887d..81b6a005eb 100644 --- a/test/hsm-emulator/src/app.ts +++ b/test/hsm-emulator/src/app.ts @@ -8,6 +8,8 @@ import { importTMK, generate3DESKeyFromComponents, generateTMK, + generateBDK, + deriveIPEK, import3DESKeyFromComponents, KeyUsage, Tr31Intent, @@ -43,6 +45,43 @@ export function createApp(port: number) { }) }) + app.post('/hsm-ilf/generate-bdk', async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { zmkUnderLmk } = requestBody + + const genBdkKey = generateBDK(AES_ILF_LMK_HEX, zmkUnderLmk) + + logger.info( + `ILF generated BDK '${genBdkKey.tr31BdkUnderZmk}|${genBdkKey.tr31BdkUnderZmk}' with KCV: ${genBdkKey.kcv}` + ) + + ffReply.code(200).send({ + tr31BdkUnderLmk: genBdkKey.tr31BdkUnderLmk, + tr31BdkUnderZmk: genBdkKey.tr31BdkUnderZmk, + kcv: genBdkKey.kcv + }) + }) + + app.post('/hsm-ilf/derive-ipek', async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31BdkUnderLmk, tr31TmkUnderLmk, ksnHex } = requestBody + const ipek = deriveIPEK( + AES_ILF_LMK_HEX, + tr31BdkUnderLmk, + tr31TmkUnderLmk, + ksnHex + ) + logger.info( + `ILF generated IPEK '${ipek.tr31IpekUnderLmk}|${ipek.tr31IpekUnderTmk}' with KCV: ${ipek.kcv}` + ) + + ffReply.code(200).send({ + tr31IpekUnderLmk: ipek.tr31IpekUnderLmk, + tr31IpekUnderTmk: ipek.tr31IpekUnderTmk, + kcv: ipek.kcv + }) + }) + app.post('/hsm-kai/import-zmk', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { component1, component2, component3, kcv } = requestBody diff --git a/test/hsm-emulator/src/card-hsm.ts b/test/hsm-emulator/src/card-hsm.ts index 311370d181..47e3d2dbcf 100644 --- a/test/hsm-emulator/src/card-hsm.ts +++ b/test/hsm-emulator/src/card-hsm.ts @@ -1,6 +1,11 @@ -import { randomBytes, createCipheriv, createDecipheriv } from 'crypto' +import { + randomBytes, + createCipheriv, + createDecipheriv, + createHash, + generateKeyPairSync +} from 'crypto' import logger from './logger' -import { generateKeyPairSync } from 'node:crypto' /** * The AES Local Master Key for the ILF HSM. @@ -16,18 +21,20 @@ const AES_KAI_LMK_HEX = * The AES Local Master Key for the Austria Card HSM. */ const AES_AUSTRIA_CARD_LMK_HEX = - 'eeeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433222211' + '11112233445566778899aabbccddeeff00112233446666778899aabbccddeeff' enum KeyUsage { ZMK, TMK, DEK, - BDK + BDK, + IPK } enum Tr31Intent { LMK, - ZMK + ZMK, + TMK } // XOR two buffers @@ -158,6 +165,121 @@ function generateTMK( } } +function generateBDK( + lmk: string, + tr31ZmkUnderLmk: string +): { + tr31BdkUnderLmk: string + tr31BdkUnderZmk: string + kcv: string +} { + const { clearKeyHex } = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmk, + tr31ZmkUnderLmk + ) + + const bdkRaw = randomBytes(24) + const bdkKCV = obtainKCVFrom3DESKey(bdkRaw) + + const tr31BdkUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmk, + KeyUsage.BDK, + 'T', + bdkRaw + ) + const tr31BdkUnderZmk = createTR31KeyBlockUnder( + Tr31Intent.ZMK, + clearKeyHex, //ZMK + KeyUsage.BDK, + 'T', + bdkRaw + ) + + return { + tr31BdkUnderLmk: tr31BdkUnderLmk.tr31Block.toString('ascii'), + tr31BdkUnderZmk: tr31BdkUnderZmk.tr31Block.toString('ascii'), + kcv: bdkKCV + } +} + +function deriveIPEK( + lmkHex: string, + tr31BdkUnderLmk: string, + tr31TmkUnderLmk: string, + ksnHex: string +): { + tr31IpekUnderLmk: string + tr31IpekUnderTmk: string + kcv: string +} { + // 1. Obtain the clear BDK key from the LMK: + const clearBdkKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmkHex, + tr31BdkUnderLmk + ) + + const ksn = Buffer.from(ksnHex, 'hex') + const bdk = clearBdkKey.clearKey + if (bdk.length !== 24 || ksn.length !== 10) { + throw new Error('BDK must be 24 bytes and KSN must be 10 bytes') + } + + // Step 1: Mask the KSN (clear rightmost 21 bits) + const ksnMasked = Buffer.from(ksn) + ksnMasked[7] &= 0xe0 + ksnMasked[8] = 0x00 + ksnMasked[9] = 0x00 + + // Step 2: Encrypt ksnMasked with original BDK (Key 1) + const cipher1 = createCipheriv('des-ede3', bdk, null) + let left = cipher1.update(ksnMasked.subarray(0, 8)) + left = Buffer.concat([left, cipher1.final()]) + + // Step 3: XOR BDK with mask + const mask = Buffer.from( + 'C0C0C0C000000000C0C0C0C000000000C0C0C0C000000000', + 'hex' + ) + const bdkMasked = xorBuffers(bdk, mask) + + // Step 4: Encrypt ksnMasked with masked BDK (Key 2) + const cipher2 = createCipheriv('des-ede3', bdkMasked, null) + let right = cipher2.update(ksnMasked.subarray(0, 8)) + right = Buffer.concat([right, cipher2.final()]) + + // Step 5: IPEK is 16-byte concat of both halves: + const ipekClear = Buffer.concat([left, right]) + const tr31IpekUnderLmk = createTR31KeyBlockUnder( + Tr31Intent.LMK, + lmkHex, + KeyUsage.IPK, + 'T', + ipekClear + ) + + const clearTmkKey = extractClearKeyFromTR31KeyBlock( + Tr31Intent.LMK, + lmkHex, + tr31TmkUnderLmk + ) + + const tr31IpekUnderTmk = createTR31KeyBlockUnder( + Tr31Intent.TMK, + clearTmkKey.clearKeyHex, // TMK + KeyUsage.IPK, + 'T', + ipekClear + ) + return { + tr31IpekUnderLmk: tr31IpekUnderLmk.tr31Block.toString('ascii'), + tr31IpekUnderTmk: tr31IpekUnderTmk.tr31Block.toString('ascii'), + kcv: tr31IpekUnderTmk.kcv + } +} + function importTMK( lmkHex: string, tr31ZmkUnderLmk: string, @@ -215,7 +337,6 @@ function importCardKey( lmkHex, tr31ZmkUnderLmk ) - // 2. Obtain the clear Card key: const clearCardKey = extractClearKeyFromTR31KeyBlock( Tr31Intent.ZMK, @@ -244,9 +365,7 @@ function importCardKey( function generateCardKey( lmk: string, - tr31ZmkUnderLmk: string, - //keySize: number = 2048, - passphrase: string = 'your-secure-passphrase' + tr31ZmkUnderLmk: string ): { tr31CardKeyUnderLmk: string tr31CardKeyUnderZmk: string @@ -263,9 +382,10 @@ function generateCardKey( }, privateKeyEncoding: { type: 'pkcs8', // Recommended for private keys - format: 'der', - cipher: 'aes-256-cbc', // Optional encryption - passphrase // Optional passphrase + format: 'der' + // We do not want encryption, as we make use of AES already. + //cipher: 'aes-256-cbc', // Optional encryption + //passphrase // Optional passphrase } }) @@ -281,23 +401,32 @@ function generateCardKey( privateKey ) - const { clearKeyHex } = extractClearKeyFromTR31KeyBlock( + const clearPvtKcv = sha512Last3Bytes(privateKey) + const clearZmkKeyHex = extractClearKeyFromTR31KeyBlock( Tr31Intent.LMK, lmk, tr31ZmkUnderLmk - ) + ).clearKeyHex const tr31CardKeyUnderZmk = createTR31KeyBlockUnder( Tr31Intent.ZMK, - clearKeyHex, //ZMK + clearZmkKeyHex, //ZMK KeyUsage.DEK, 'T', privateKey ) + if (tr31CardKeyUnderZmk.kcv !== clearPvtKcv) { + throw new Error( + `PvtKey under ZMK failure. Expected KCV '${clearPvtKcv}' but got '${tr31CardKeyUnderZmk.kcv}'.` + ) + } + + const tr31CardKeyUnderLmkAscii = + tr31CardKeyUnderLmk.tr31Block.toString('ascii') const pvtKey = extractClearKeyFromTR31KeyBlock( Tr31Intent.LMK, lmk, - tr31CardKeyUnderLmk.tr31Block.toString('ascii') + tr31CardKeyUnderLmkAscii ) logger.debug(`Private key [BACK] size is ${pvtKey.clearKey.length} bytes.`) @@ -306,19 +435,24 @@ function generateCardKey( tr31CardKeyUnderLmk: tr31CardKeyUnderLmk.tr31Block.toString('ascii'), tr31CardKeyUnderZmk: tr31CardKeyUnderZmk.tr31Block.toString('ascii'), publicKey, - kcv: 'XXXXXX' + kcv: clearPvtKcv } } -function obtainKCVFrom3DESKey(key: Buffer): string { +function obtainKCVFrom3DESKey(key: Buffer | Uint8Array): string { const data = Buffer.alloc(8, 0x00) // 8 bytes of zeros const cipher = createCipheriv('des-ede3', key, null) const encrypted = Buffer.concat([cipher.update(data), cipher.final()]) - return encrypted.slice(0, 3).toString('hex').toUpperCase() // First 3 bytes + return encrypted.subarray(0, 3).toString('hex').toUpperCase() // First 3 bytes +} + +function sha512Last3Bytes(input: Buffer | Uint8Array): string { + const hexSha = createHash('sha512').update(input).digest('hex') + return hexSha.slice(-6).toUpperCase() } function encryptWithAES256( - plaintext: Buffer, + plaintext: Buffer | Uint8Array, aesKeyHex: string, zeroIv: boolean = true ): { iv: Buffer; ciphertext: Buffer; ciphertextHex: string } { @@ -339,7 +473,7 @@ function encryptWithAES256( } function encryptWith3DES( - plaintext: Buffer, + plaintext: Buffer | Uint8Array, keyHex: string, zeroIv: boolean = true ): { iv: Buffer; ciphertext: Buffer; ciphertextHex: string } { @@ -370,6 +504,18 @@ function decryptWithAES256( const key = Buffer.from(aesKeyHex, 'hex') const decipher = createDecipheriv('aes-256-cbc', key, iv) + logger.info( + 'decipher.decryptWithAES256: ' + + ciphertext.length + + ' <-> ' + + key.length + + ' <-> ' + + iv.length + + ' | ' + + key + + ' | ' + + ciphertext + ) return Buffer.concat([decipher.update(ciphertext), decipher.final()]) } @@ -390,25 +536,36 @@ function decryptWith3DES( function createTR31KeyBlockUnder( intent: Tr31Intent, kekHex: string, // 32-byte AES key (hex-encoded) - keyUsage: KeyUsage, // 3 chars, e.g., 'EK' for Encryption Key + keyUsage: KeyUsage, // 3 chars, e.g., 'DEK' for Encryption Key keyType: string, // 1 char, e.g., 'T' for TDEA, 'A' for AES - key: Buffer, // Key material (e.g., 24 bytes for 3DES) + key: Buffer | Uint8Array, // Key material (e.g., 24 bytes for 3DES) zeroIv: boolean = true -): { iv: Buffer; tr31Block: Buffer } { +): { iv: Buffer; tr31Block: Buffer; kcv: string } { if (intent == Tr31Intent.LMK && kekHex.length !== 64) - throw new Error('KEK (LMK) must be 64 hex chars (32 bytes)') - else if (intent == Tr31Intent.ZMK && kekHex.length !== 48) - throw new Error('KEK (ZMK) must be 48 hex chars (24 bytes)') + throw new Error( + `KEK (LMK) must be 64 hex chars (32 bytes), currently ${kekHex.length}` + ) + //AES + else if ( + (intent == Tr31Intent.ZMK || intent == Tr31Intent.TMK) && + kekHex.length !== 48 + ) + throw new Error( + `KEK (ZMK) must be 48 hex chars (24 bytes), currently ${kekHex.length}` + ) //3DES + + const kcv = isKeyUsageForNon3DS(keyUsage) + ? sha512Last3Bytes(key) + : obtainKCVFrom3DESKey(key) // for data, we convert to ASCII-HEX: - if (keyUsage === KeyUsage.DEK) key = Buffer.from(key.toString('hex')) + if (isKeyUsageForNon3DS(keyUsage)) key = Buffer.from(key.toString('hex')) // Encrypt using AES-256-CBC const encryptedKey = intent == Tr31Intent.LMK ? encryptWithAES256(key, kekHex, zeroIv) : encryptWith3DES(key, kekHex, zeroIv) - const encryptedKeyHex = encryptedKey.ciphertextHex.toUpperCase() // TR-31 Header – 16 bytes (simplified) @@ -433,7 +590,6 @@ function createTR31KeyBlockUnder( length + reserved2 const header = Buffer.from(headerStr, 'ascii') // total 16 bytes - const kcv = keyUsage === KeyUsage.DEK ? 'XXXXXX' : obtainKCVFrom3DESKey(key) // Combine header and key const tr31Block = Buffer.concat([ @@ -444,7 +600,17 @@ function createTR31KeyBlockUnder( return { iv: encryptedKey.iv, - tr31Block + tr31Block, + kcv + } +} + +function isKeyUsageForNon3DS(keyUsage: KeyUsage): boolean { + switch (keyUsage) { + case (KeyUsage.DEK, KeyUsage.IPK): + return true + default: + return false } } @@ -465,12 +631,13 @@ function extractClearKeyFromTR31KeyBlock( intent === Tr31Intent.LMK ? decryptWithAES256(tr31EncKeyPortion, kekHex, Buffer.alloc(16)) : decryptWith3DES(tr31EncKeyPortion, kekHex, Buffer.alloc(8)) - if (usage == KeyUsage.DEK) + if (isKeyUsageForNon3DS(usage)) clearKey = Buffer.from(clearKey.toString('ascii'), 'hex') const tr31Kcv = tr31.slice(-6) - const kcvComputed = - usage == KeyUsage.DEK ? 'XXXXXX' : obtainKCVFrom3DESKey(clearKey) + const kcvComputed = isKeyUsageForNon3DS(usage) + ? sha512Last3Bytes(clearKey) + : obtainKCVFrom3DESKey(clearKey) if (kcvComputed !== tr31Kcv) { throw new Error( `Expected KCV '${tr31Kcv}' but got '${kcvComputed}'. Please confirm correct KEK is used.` @@ -490,6 +657,8 @@ export { obtainKCVFrom3DESKey, createTR31KeyBlockUnder, generateTMK, + generateBDK, + deriveIPEK, importTMK, generateCardKey, importCardKey, From a0b7d863bf816313a12805ee22298eeb8c26d705 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Sat, 17 May 2025 09:54:23 -0600 Subject: [PATCH 10/13] feat(car-6): fix spelling. --- test/hsm-emulator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index a4c6fa016c..36a8b3360f 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -12,7 +12,7 @@ developers to validate HSM-integrated workflows without requiring access to a ph The parties involved in the HSM emulator include: -- **ILF** - The ILF will be the activing ASE (Accont Serving Entity) +- **ILF** - The ILF will be the acting ASE (Account Serving Entity) - **KaiOS** - The POS manufacturer - **Austria Card** - The card issuer From 19121512965bcde73360b56e4c4dbcd159daf36e Mon Sep 17 00:00:00 2001 From: koekiebox Date: Tue, 10 Jun 2025 13:29:00 +0200 Subject: [PATCH 11/13] feat(car-6): update doc and endpoints based on ASE model. --- ...bru => 1- Customer ASE - Generate ZMK.bru} | 4 +- .../1- Merchant ASE - Generate ZMK.bru | 36 +++ ...K.bru => 2- Austria Card - Import ZMK.bru} | 6 +- ...port ZMK.bru => 2- KaiOS - Import ZMK.bru} | 6 +- ... Customer ASE - Generate Card Keypair.bru} | 6 +- ...te TMK.bru => 3- KaiOS - Generate TMK.bru} | 6 +- ...Austria Card - Import Card PrivateKey.bru} | 6 +- ...K.bru => 4- Merchant ASE - Import TMK.bru} | 6 +- ...bru => 5- Merchant ASE - Generate BDK.bru} | 6 +- ....bru => 6- Merchant ASE - Derive IPEK.bru} | 6 +- packages/auth/src/signature/middleware.ts | 6 +- pnpm-lock.yaml | 296 +++++++++++++++--- test/hsm-emulator/README.md | 106 +++++-- test/hsm-emulator/src/app.ts | 208 +++++++----- test/hsm-emulator/src/card-hsm.ts | 16 +- 15 files changed, 536 insertions(+), 184 deletions(-) rename bruno/collections/Rafiki/HSM Emulator/{HSM ILF - Generate ZMK.bru => 1- Customer ASE - Generate ZMK.bru} (87%) create mode 100644 bruno/collections/Rafiki/HSM Emulator/1- Merchant ASE - Generate ZMK.bru rename bruno/collections/Rafiki/HSM Emulator/{HSM AustriaCard - Import ZMK.bru => 2- Austria Card - Import ZMK.bru} (83%) rename bruno/collections/Rafiki/HSM Emulator/{HSM KaiOS - Import ZMK.bru => 2- KaiOS - Import ZMK.bru} (85%) rename bruno/collections/Rafiki/HSM Emulator/{HSM ILF - Generate Card Keypair.bru => 3- Customer ASE - Generate Card Keypair.bru} (84%) rename bruno/collections/Rafiki/HSM Emulator/{HSM KaiOS - Generate TMK.bru => 3- KaiOS - Generate TMK.bru} (87%) rename bruno/collections/Rafiki/HSM Emulator/{HSM AustriaCard - Import Card PrivateKey.bru => 4- Austria Card - Import Card PrivateKey.bru} (81%) rename bruno/collections/Rafiki/HSM Emulator/{HSM ILF - Import TMK.bru => 4- Merchant ASE - Import TMK.bru} (83%) rename bruno/collections/Rafiki/HSM Emulator/{HSM ILF - Generate BDK.bru => 5- Merchant ASE - Generate BDK.bru} (82%) rename bruno/collections/Rafiki/HSM Emulator/{HSM ILF - Derive IPEK.bru => 6- Merchant ASE - Derive IPEK.bru} (86%) diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/1- Customer ASE - Generate ZMK.bru similarity index 87% rename from bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru rename to bruno/collections/Rafiki/HSM Emulator/1- Customer ASE - Generate ZMK.bru index 537c8290d6..4050c6d6fb 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate ZMK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/1- Customer ASE - Generate ZMK.bru @@ -1,11 +1,11 @@ meta { - name: HSM ILF - Generate ZMK + name: 1: Customer ASE - Generate ZMK type: http seq: 1 } post { - url: {{hsmEmulatorHost}}/hsm-ilf/generate-zmk + url: {{hsmEmulatorHost}}/hsm/ase-customer/generate-zmk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/1- Merchant ASE - Generate ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/1- Merchant ASE - Generate ZMK.bru new file mode 100644 index 0000000000..5b83fb786c --- /dev/null +++ b/bruno/collections/Rafiki/HSM Emulator/1- Merchant ASE - Generate ZMK.bru @@ -0,0 +1,36 @@ +meta { + name: 1: Merchant ASE - Generate ZMK + type: http + seq: 5 +} + +post { + url: {{hsmEmulatorHost}}/hsm/ase-merchant/generate-zmk + body: json + auth: none +} + +headers { + Content-Type: application/json +} + +body:json { + {} +} + +script:post-response { + const body = res.getBody(); + + if (body?.component1) bru.setEnvVar("zmk_component1", body.component1); + if (body?.component2) bru.setEnvVar("zmk_component2", body.component2); + if (body?.component3) bru.setEnvVar("zmk_component3", body.component3); + if (body?.kcv) bru.setEnvVar("zmk_kcv", body.kcv); + if (body?.tr31Block) bru.setEnvVar("zmk_tr31_lmk_ilf", body.tr31Block); + +} + +tests { + test("Status code is 200", function() { + expect(res.getStatus()).to.equal(200); + }); +} diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/2- Austria Card - Import ZMK.bru similarity index 83% rename from bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru rename to bruno/collections/Rafiki/HSM Emulator/2- Austria Card - Import ZMK.bru index d1579b61c5..6a9b0c76ce 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import ZMK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/2- Austria Card - Import ZMK.bru @@ -1,11 +1,11 @@ meta { - name: HSM AustriaCard - Import ZMK + name: 2: Austria Card - Import ZMK type: http - seq: 4 + seq: 2 } post { - url: {{hsmEmulatorHost}}/hsm-austria-card/import-zmk + url: {{hsmEmulatorHost}}/hsm/austria-card/import-zmk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru b/bruno/collections/Rafiki/HSM Emulator/2- KaiOS - Import ZMK.bru similarity index 85% rename from bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru rename to bruno/collections/Rafiki/HSM Emulator/2- KaiOS - Import ZMK.bru index e34a5372e0..3904411867 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Import ZMK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/2- KaiOS - Import ZMK.bru @@ -1,11 +1,11 @@ meta { - name: HSM KaiOS - Import ZMK + name: 2: KaiOS - Import ZMK type: http - seq: 3 + seq: 6 } post { - url: {{hsmEmulatorHost}}/hsm-kai/import-zmk + url: {{hsmEmulatorHost}}/hsm/kai-os/import-zmk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru b/bruno/collections/Rafiki/HSM Emulator/3- Customer ASE - Generate Card Keypair.bru similarity index 84% rename from bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru rename to bruno/collections/Rafiki/HSM Emulator/3- Customer ASE - Generate Card Keypair.bru index 06b7326b25..18d495afd7 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate Card Keypair.bru +++ b/bruno/collections/Rafiki/HSM Emulator/3- Customer ASE - Generate Card Keypair.bru @@ -1,11 +1,11 @@ meta { - name: HSM ILF - Generate Card Keypair + name: 3: Customer ASE - Generate Card Keypair type: http - seq: 8 + seq: 3 } post { - url: {{hsmEmulatorHost}}/hsm-ilf/generate-card-key + url: {{hsmEmulatorHost}}/hsm/ase-customer/generate-card-key body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru b/bruno/collections/Rafiki/HSM Emulator/3- KaiOS - Generate TMK.bru similarity index 87% rename from bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru rename to bruno/collections/Rafiki/HSM Emulator/3- KaiOS - Generate TMK.bru index d85009e9a8..38177debb2 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM KaiOS - Generate TMK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/3- KaiOS - Generate TMK.bru @@ -1,11 +1,11 @@ meta { - name: HSM KaiOS - Generate TMK + name: 3: KaiOS - Generate TMK type: http - seq: 5 + seq: 7 } post { - url: {{hsmEmulatorHost}}/hsm-kai/generate-tmk + url: {{hsmEmulatorHost}}/hsm/kai-os/generate-tmk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru b/bruno/collections/Rafiki/HSM Emulator/4- Austria Card - Import Card PrivateKey.bru similarity index 81% rename from bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru rename to bruno/collections/Rafiki/HSM Emulator/4- Austria Card - Import Card PrivateKey.bru index d0fe5528dc..c9f12b4d6b 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM AustriaCard - Import Card PrivateKey.bru +++ b/bruno/collections/Rafiki/HSM Emulator/4- Austria Card - Import Card PrivateKey.bru @@ -1,11 +1,11 @@ meta { - name: HSM AustriaCard - Import Card PrivateKey + name: 4: Austria Card - Import Card PrivateKey type: http - seq: 9 + seq: 4 } post { - url: {{hsmEmulatorHost}}/hsm-austria-card/import-card-key + url: {{hsmEmulatorHost}}/hsm/austria-card/import-card-key body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru b/bruno/collections/Rafiki/HSM Emulator/4- Merchant ASE - Import TMK.bru similarity index 83% rename from bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru rename to bruno/collections/Rafiki/HSM Emulator/4- Merchant ASE - Import TMK.bru index 99038bf81c..0da63c5703 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Import TMK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/4- Merchant ASE - Import TMK.bru @@ -1,11 +1,11 @@ meta { - name: HSM ILF - Import TMK + name: 4: Merchant ASE - Import TMK type: http - seq: 6 + seq: 8 } post { - url: {{hsmEmulatorHost}}/hsm-ilf/import-tmk + url: {{hsmEmulatorHost}}/hsm/ase-merchant/import-tmk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru b/bruno/collections/Rafiki/HSM Emulator/5- Merchant ASE - Generate BDK.bru similarity index 82% rename from bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru rename to bruno/collections/Rafiki/HSM Emulator/5- Merchant ASE - Generate BDK.bru index d56c03d088..083253ea5c 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Generate BDK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/5- Merchant ASE - Generate BDK.bru @@ -1,11 +1,11 @@ meta { - name: HSM ILF - Generate BDK + name: 5: Merchant ASE - Generate BDK type: http - seq: 2 + seq: 9 } post { - url: {{hsmEmulatorHost}}/hsm-ilf/generate-bdk + url: {{hsmEmulatorHost}}/hsm/ase-merchant/generate-bdk body: json auth: none } diff --git a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru b/bruno/collections/Rafiki/HSM Emulator/6- Merchant ASE - Derive IPEK.bru similarity index 86% rename from bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru rename to bruno/collections/Rafiki/HSM Emulator/6- Merchant ASE - Derive IPEK.bru index 5c054bc82b..9bb3534e69 100644 --- a/bruno/collections/Rafiki/HSM Emulator/HSM ILF - Derive IPEK.bru +++ b/bruno/collections/Rafiki/HSM Emulator/6- Merchant ASE - Derive IPEK.bru @@ -1,11 +1,11 @@ meta { - name: HSM ILF - Derive IPEK + name: 6: Merchant ASE - Derive IPEK type: http - seq: 7 + seq: 10 } post { - url: {{hsmEmulatorHost}}/hsm-ilf/derive-ipek + url: {{hsmEmulatorHost}}/hsm/ase-merchant/derive-ipek body: json auth: none } diff --git a/packages/auth/src/signature/middleware.ts b/packages/auth/src/signature/middleware.ts index 1b8e63c0c5..3980fb2dd9 100644 --- a/packages/auth/src/signature/middleware.ts +++ b/packages/auth/src/signature/middleware.ts @@ -66,7 +66,7 @@ export async function grantContinueHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'invalid signature headers' + 'continue: invalid signature headers' ) } @@ -119,7 +119,7 @@ export async function grantInitiationHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'invalid signature headers' + 'initiate: invalid signature headers' ) } @@ -144,7 +144,7 @@ export async function tokenHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'invalid signature headers' + 'token: invalid signature headers' ) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 180d8e11d3..be3ad06b56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -746,6 +746,28 @@ importers: specifier: ^6.7.6 version: 6.7.6 + test/hsm-emulator: + dependencies: + fastify: + specifier: ^5.2.1 + version: 5.3.3 + pino: + specifier: ^9.6.0 + version: 9.7.0 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.14.15 + '@types/pg': + specifier: ^8.11.11 + version: 8.15.4 + tsx: + specifier: ^4.19.3 + version: 4.19.4 + typescript: + specifier: ^5.0.0 + version: 5.8.3 + test/integration: devDependencies: '@interledger/open-payments': @@ -3983,11 +4005,46 @@ packages: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + /@fastify/ajv-compiler@4.0.2: + resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + dev: false + /@fastify/busboy@2.1.1: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} dev: true + /@fastify/error@4.2.0: + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + dev: false + + /@fastify/fast-json-stringify-compiler@5.0.3: + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + dependencies: + fast-json-stringify: 6.0.1 + dev: false + + /@fastify/forwarded@3.0.0: + resolution: {integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==} + dev: false + + /@fastify/merge-json-schemas@0.2.1: + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + dependencies: + dequal: 2.0.3 + dev: false + + /@fastify/proxy-addr@5.0.0: + resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==} + dependencies: + '@fastify/forwarded': 3.0.0 + ipaddr.js: 2.2.0 + dev: false + /@graphql-codegen/add@5.0.3(graphql@16.11.0): resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==} peerDependencies: @@ -6124,7 +6181,7 @@ packages: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.3 + semver: 7.7.2 dev: true /@npmcli/git@4.1.0: @@ -6137,7 +6194,7 @@ packages: proc-log: 3.0.0 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.6.3 + semver: 7.7.2 which: 3.0.1 transitivePeerDependencies: - bluebird @@ -8269,9 +8326,16 @@ packages: /@types/pg-pool@2.0.4: resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} dependencies: - '@types/pg': 8.6.1 + '@types/pg': 8.15.4 dev: false + /@types/pg@8.15.4: + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} + dependencies: + '@types/node': 20.14.15 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + /@types/pg@8.6.1: resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} dependencies: @@ -8448,7 +8512,7 @@ packages: grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 - semver: 7.6.3 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8618,7 +8682,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8639,7 +8703,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8661,7 +8725,7 @@ packages: globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 - semver: 7.6.3 + semver: 7.7.2 ts-api-utils: 1.0.1(typescript@5.4.3) typescript: 5.4.3 transitivePeerDependencies: @@ -8682,7 +8746,7 @@ packages: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -8702,7 +8766,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -8721,7 +8785,7 @@ packages: '@typescript-eslint/types': 7.5.0 '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) eslint: 8.57.1 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -9082,6 +9146,10 @@ packages: dependencies: event-target-shim: 5.0.1 + /abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -9140,7 +9208,7 @@ packages: resolution: {integrity: sha512-hCOfMzbFx5IDutmWLAt6MZwOUjIfSM9G9FyVxytmE4Rs/5YDPWQrD/+IR1w+FweD9H2oOZEnv36TmkjhNURBVA==} dev: true - /ajv-formats@2.1.1(ajv@8.12.0): + /ajv-formats@2.1.1(ajv@8.17.1): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: ajv: ^8.0.0 @@ -9148,11 +9216,10 @@ packages: ajv: optional: true dependencies: - ajv: 8.12.0 - dev: true + ajv: 8.17.1 - /ajv-formats@2.1.1(ajv@8.17.1): - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + /ajv-formats@3.0.1(ajv@8.17.1): + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: ajv: ^8.0.0 peerDependenciesMeta: @@ -9160,6 +9227,7 @@ packages: optional: true dependencies: ajv: 8.17.1 + dev: false /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} @@ -9194,6 +9262,7 @@ packages: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 + dev: false /ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} @@ -9777,6 +9846,13 @@ packages: dependencies: possible-typed-array-names: 1.0.0 + /avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.19.1 + dev: false + /axe-core@4.10.2: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} @@ -10193,7 +10269,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.6.3 + semver: 7.7.2 dev: true /busboy@1.6.0: @@ -12750,7 +12826,6 @@ packages: /fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - dev: true /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -12785,6 +12860,17 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-json-stringify@6.0.1: + resolution: {integrity: sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==} + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + json-schema-ref-resolver: 2.0.1 + rfdc: 1.4.1 + dev: false + /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true @@ -12793,7 +12879,6 @@ packages: resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 - dev: true /fast-redact@3.1.2: resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} @@ -12816,11 +12901,37 @@ packages: engines: {node: '>= 4.9.1'} dev: true + /fastify@5.3.3: + resolution: {integrity: sha512-nCBiBCw9q6jPx+JJNVgO8JVnTXeUyrGcyTKPQikRkA/PanrFcOIo4R+ZnLeOLPZPGgzjomqfVarzE0kYx7qWiQ==} + dependencies: + '@fastify/ajv-compiler': 4.0.2 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.0.0 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.0.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 9.7.0 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.0.0 + semver: 7.7.2 + toad-cache: 3.7.0 + dev: false + /fastq@1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: reusify: 1.0.4 + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + dependencies: + reusify: 1.0.4 + dev: false + /fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} dependencies: @@ -12911,6 +13022,15 @@ packages: pkg-dir: 7.0.0 dev: true + /find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.1 + safe-regex2: 5.0.0 + dev: false + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -13200,6 +13320,12 @@ packages: get-intrinsic: 1.2.4 dev: true + /get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-tsconfig@4.5.0: resolution: {integrity: sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==} dev: true @@ -14147,6 +14273,11 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + /ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + dev: false + /iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} dev: false @@ -14659,7 +14790,7 @@ packages: '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -15096,7 +15227,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -15236,6 +15367,12 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /json-schema-ref-resolver@2.0.1: + resolution: {integrity: sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==} + dependencies: + dequal: 2.0.3 + dev: false + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -15526,6 +15663,14 @@ packages: type-check: 0.4.0 dev: true + /light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + dependencies: + cookie: 1.0.2 + process-warning: 4.0.1 + set-cookie-parser: 2.7.1 + dev: false + /lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -17274,7 +17419,7 @@ packages: dependencies: hosted-git-info: 6.1.1 is-core-module: 2.13.0 - semver: 7.6.3 + semver: 7.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -17301,7 +17446,7 @@ packages: resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.3 + semver: 7.7.2 dev: true /npm-normalize-package-bin@3.0.1: @@ -17315,7 +17460,7 @@ packages: dependencies: hosted-git-info: 6.1.1 proc-log: 3.0.0 - semver: 7.6.3 + semver: 7.7.2 validate-npm-package-name: 5.0.0 dev: true @@ -17326,7 +17471,7 @@ packages: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 - semver: 7.6.3 + semver: 7.7.2 dev: true /npm-run-all2@6.2.6: @@ -17575,15 +17720,15 @@ packages: /openapi-response-validator@9.3.1: resolution: {integrity: sha512-2AOzHAbrwdj5DNL3u+BadhfmL3mlc3mmCv6cSAsEjoMncpOOVd95JyMf0j0XUyJigJ8/ILxnhETfg35vt1pGSQ==} dependencies: - ajv: 8.12.0 + ajv: 8.17.1 openapi-types: 9.3.1 dev: true /openapi-schema-validator@9.3.1: resolution: {integrity: sha512-5wpFKMoEbUcjiqo16jIen3Cb2+oApSnYZpWn8WQdRO2q/dNQZZl8Pz6ESwCriiyU5AK4i5ZI6+7O3bHQr6+6+g==} dependencies: - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) lodash.merge: 4.6.2 openapi-types: 9.3.1 dev: true @@ -17953,7 +18098,6 @@ packages: /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - dev: false /pg-pool@3.6.1(pg@8.11.3): resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} @@ -17965,7 +18109,6 @@ packages: /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -17976,7 +18119,6 @@ packages: postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 - dev: false /pg@8.11.3: resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} @@ -18033,6 +18175,12 @@ packages: readable-stream: 4.1.0 split2: 4.1.0 + /pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + dependencies: + split2: 4.1.0 + dev: false + /pino-pretty@11.0.0: resolution: {integrity: sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==} hasBin: true @@ -18055,6 +18203,10 @@ packages: /pino-std-serializers@6.0.0: resolution: {integrity: sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==} + /pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + dev: false + /pino@8.19.0: resolution: {integrity: sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==} hasBin: true @@ -18071,6 +18223,23 @@ packages: sonic-boom: 3.7.0 thread-stream: 2.1.0 + /pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.3.1 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + dev: false + /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -18282,24 +18451,20 @@ packages: /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - dev: false /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} - dev: false /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - dev: false /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 - dev: false /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -18370,6 +18535,14 @@ packages: /process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + /process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + dev: false + + /process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + dev: false + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -19177,6 +19350,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -19219,6 +19396,11 @@ packages: signal-exit: 3.0.7 dev: true + /ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + dev: false + /retext-latin@4.0.0: resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} dependencies: @@ -19270,6 +19452,10 @@ packages: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: false + /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -19437,6 +19623,12 @@ packages: is-regex: 1.2.1 dev: true + /safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + dependencies: + ret: 0.5.0 + dev: false + /safe-stable-stringify@2.3.1: resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} engines: {node: '>=10'} @@ -19483,6 +19675,10 @@ packages: /secure-json-parse@2.5.0: resolution: {integrity: sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w==} + /secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + dev: false + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -19518,8 +19714,6 @@ packages: engines: {node: '>=10'} hasBin: true requiresBuild: true - dev: false - optional: true /send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} @@ -19646,7 +19840,7 @@ packages: dependencies: color: 4.2.3 detect-libc: 2.0.3 - semver: 7.7.1 + semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 '@img/sharp-darwin-x64': 0.33.5 @@ -19890,6 +20084,12 @@ packages: dependencies: atomic-sleep: 1.0.0 + /sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -20588,6 +20788,12 @@ packages: dependencies: real-require: 0.2.0 + /thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + dependencies: + real-require: 0.2.0 + dev: false + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -20667,6 +20873,11 @@ packages: dependencies: is-number: 7.0.0 + /toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -20857,6 +21068,17 @@ packages: typescript: 5.8.3 dev: true + /tsx@4.19.4: + resolution: {integrity: sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /turbo-stream@2.4.0: resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} diff --git a/test/hsm-emulator/README.md b/test/hsm-emulator/README.md index 36a8b3360f..b80f312682 100644 --- a/test/hsm-emulator/README.md +++ b/test/hsm-emulator/README.md @@ -3,6 +3,8 @@ The `hsm-emulator` is a lightweight emulator that simulates the behavior of a Hardware Security Module (HSM) for development and testing purposes. +Please see: https://docs.google.com/document/d/12GGzU-HC9xAccj9t7EzLkrPEaSGZWCtMhGpjcwWBmG8/edit?tab=t.0 + ## Overview The HSM Emulator provides cryptographic operations such as key management between card and processor parties, enabling @@ -12,9 +14,10 @@ developers to validate HSM-integrated workflows without requiring access to a ph The parties involved in the HSM emulator include: -- **ILF** - The ILF will be the acting ASE (Account Serving Entity) +- **Customer ASE** - The Account Servicing Entity (ASE) who manages the card for the customer (issuer) +- **Merchant ASE** - The ASE who manages the POS device (acquirer) - **KaiOS** - The POS manufacturer -- **Austria Card** - The card issuer +- **Austria Card** - The card personalization / printer ## Build HSM Emulator: @@ -37,21 +40,36 @@ The ZMK may be generated by either of two or more parties. One party is responsi other party is responsible for importing the ZMK. It is strongly advised that the ZMK be generated/imported using an HSM. The KCV is used to verify integrity during the exchange. +### 1.1.1 ZMK - Customer ASE (Issuer) + +The ZMK generated between Customer ASE and Austria Card. + +```mermaid +--- +title: ZMK Exchange - Card +--- +erDiagram + "Customer ASE 🏦" ||--}| "ZMK πŸ”‘" : "generates key" + "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : "imports key (3x clear components)" +``` + +### 1.1.2 ZMK - Merchant ASE (Acquirer) + +The ZMK generated between Merchant ASE and KaiOS. + ```mermaid --- -title: ZMK Exchange +title: ZMK Exchange - POS --- erDiagram - "ILF 🏦" ||--}| "ZMK πŸ”‘" : generates - "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : "imports (3x clear components)" - "Austria Card πŸ’³" ||--|| "ZMK πŸ”‘" : "imports (3x clear components)" + "Merchant ASE 🏦" ||--}| "ZMK πŸ”‘" : "generates key" + "KaiOS πŸ“±" ||--|| "ZMK πŸ”‘" : "imports key (3x clear components)" ``` ### 1.2. TMK - Terminal Master Key -The Terminal Master Key (TMK) is generated by the POS or terminal manufacturer and is unique to each terminal. -It is securely transferred to the Account Serving Entity (ASE), -encrypted under the Zone Master Key (ZMK) using the TR-31 key block format. +The Terminal Master Key (TMK) is generated by the terminal POS manufacturer and is unique to each terminal. +The TMK is securely transferred to the Merchant ASE encrypted under the Zone Master Key (ZMK) using the TR-31 key block format. The TMK facilitates the secure delivery of session keys between the ASE and the terminal, ensuring encrypted communication and key management. @@ -61,23 +79,25 @@ title: TMK Exchange --- erDiagram "KaiOS πŸ“±" ||--}| "TMK πŸ”‘" : generates - "ILF 🏦" ||--}| "TMK πŸ”‘" : "imports (under ZMK)" + "Merchant ASE 🏦" ||--}| "TMK πŸ”‘" : "imports (under ZMK)" ``` ### 1.3. PIN/SRED BDK and IPEK - Base Derivation Key and Initial PIN Encryption Keys -The Base Derivation Keys (BDKs) are generated by the Account Serving Entity (ASE) and typically remain consistent for each terminal -manufacturer or model. From the BDK, Initial PIN Encryption Keys (IPEKs) are derived and securely loaded onto the terminal, -encrypted under the Terminal Master Key (TMK) using the TR-31 key block format. -This process ensures secure delivery of keys to the terminal, either through direct injection or via an over-the-air Remote Key Injection (RKI) mechanism. +The Base Derivation Keys (BDKs), generated by the Merchant’s ASE, are typically fixed per terminal manufacturer and model. +From each BDK, Initial PIN Encryption Keys (IPEKs) are derived and securely loaded onto the terminal. +These IPEKs are encrypted under the Terminal Master Key (TMK) using the TR-31 key block format, ensuring secure key delivery. +The distribution can occur either through direct injection or remotely via an over-the-air Remote Key Injection (RKI) process. + +The PIN and SRED IPEK's will not share a BDK (See PCI-PIN Security Requirement ref #18-3). ```mermaid --- title: PIN/SRED Key Exchange and IPEK --- erDiagram - "ILF 🏦" ||--|| "SRED/PIN BDK πŸ”‘" : "generates" - "SRED/PIN BDK πŸ”‘" ||--}| "IPEK πŸ”‘" : "generates (based on BDK)" + "Merchant ASE 🏦" ||--|| "SRED / PIN BDK πŸ”‘" : "generates" + "SRED / PIN BDK πŸ”‘" ||--}| "IPEK πŸ”‘" : "generates derived IPEK (based on BDK)" "Terminal πŸ“±" ||--|| "IPEK πŸ”‘" : "imports (under TMK)" ``` @@ -92,31 +112,53 @@ The public key from this pair is securely linked to a corresponding wallet addre title: Card Key Generation and Issuing --- erDiagram - "ILF 🏦" ||--}| "Card KeyPair πŸ”" : "generates" + "Customer ASE 🏦" ||--}| "Card KeyPair πŸ”" : "generates" "Card KeyPair πŸ”" ||--}| "Private Key πŸ”‘" : "has" "Card KeyPair πŸ”" ||--}| "Public Key πŸ”“" : "has" - "Public Key πŸ”“" |{--}| "Wallet Address πŸ“‡" : "ILF store against wallet address πŸ’½" + "Public Key πŸ”“" |{--}| "Wallet Address πŸ“‡" : "store against wallet address πŸ’½" "Austria Card πŸ’³" ||--}| "Private Key πŸ”‘" : "imports (under ZMK)" "Private Key πŸ”‘" ||--|| "Card πŸ’³" : "loaded onto (securely during issuing)" ``` +### 2.2 POS Key - POS Asymmetric Keys + +The POS Key Pair is generated by the merchant Account Serving Entity (ASE), which acts as the card acceptor / merchant. +A unique key pair is created for each POS terminal. The generation would take place when the POS terminal is linked to the merchant ASE. +The public key from this pair is securely linked to a corresponding merchant wallet address. + +```mermaid +--- +title: POS Key Generation and Registration +--- +erDiagram + "Merchant ASE 🏦" ||--}| "POS KeyPair πŸ”" : "generates" + "POS KeyPair πŸ”" ||--}| "Private Key πŸ”‘" : "has" + "POS KeyPair πŸ”" ||--}| "Public Key πŸ”“" : "has" + "Public Key πŸ”“" |{--}| "Merchant Wallet Address DB πŸ“‡" : "store against merchant wallet address πŸ’½" + + "Terminal πŸ“±" ||--}| "Private Key πŸ”‘" : "imports (under TMK)" + + "Private Key πŸ”‘" ||--|| "Terminal πŸ“±" : "loaded onto (securely during registration)" +``` + ## Glossary of Terms Terms of definition related to ASE, Card issuer and terminal manufacturers. -| Term | Description | -| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | -| LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | -| ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | -| TMK | Terminal Master Key. Used to secure the transmission of working keys (like PIN, SRED encryption keys or MAC keys) between a terminal and the HSM. | -| BDK | Base Derivation Key, is the root key from which a unique IPEK (Initial PIN Encryption Key) is derived for each device or terminal. The IPEK, in turn, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | -| IPEK | Initial PIN Encryption Key, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | -| KSN | Key Serial Number. A unique identifier for each device, used in key derivation. | -| Session Key | Derived from the IPEK for encrypting a single transaction. | -| DUKPT | DUKPT stands for Derived Unique Key Per Transaction. It’s a key management scheme used primarily in payment systems (e.g. POS terminals) to ensure each transaction is encrypted with a unique key, dramatically reducing the risk of compromise. | -| KCV | Key Check Value. Short cryptographic value derived from a key, used to verify that the key has been correctly transferred or entered without revealing the key itself. | -| POS | Point of Service device (also referred to as the terminal). | -| TR-31 | A TR-31 key block is a standardized format used to securely exchange cryptographic keys between systems, especially in financial environments involving HSMs (Hardware Security Modules). It was defined by the ANSI X9.24-1 standard. | +| Term | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| HSM | Hardware Security Module. Physical device that provides secure key storage and cryptographic processing. It is designed to protect sensitive data and perform secure operations. | +| LMK | Local Master Key. Top-level encryption key used to secure and manage other keys within the HSM. It plays a central role in the HSM’s key hierarchy. | +| ZMK | Zone Master Key. Cryptographic key used to securely exchange other encryption keys between two systems or organizations β€” typically between two HSMs (Hardware Security Modules) that are part of different cryptographic zones. | +| TMK | Terminal Master Key. Used to secure the transmission of working keys (like PIN, SRED encryption keys or MAC keys) between a terminal and the HSM. | +| BDK | Base Derivation Key, is the root key from which a unique IPEK (Initial PIN Encryption Key) is derived for each device or terminal. The IPEK, in turn, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | +| IPEK | Initial PIN Encryption Key, is used to derive session keys for each transaction, ensuring that no two transactions share the same encryption key. | +| KSN | Key Serial Number. A unique identifier for each device, used in key derivation. | +| Session Key | Derived from the IPEK for encrypting a single transaction. | +| DUKPT | DUKPT stands for Derived Unique Key Per Transaction. It’s a key management scheme used primarily in payment systems (e.g. POS terminals) to ensure each transaction is encrypted with a unique key, dramatically reducing the risk of compromise. | +| KCV | Key Check Value. Short cryptographic value derived from a key, used to verify that the key has been correctly transferred or entered without revealing the key itself. | +| POS | Point of Service device (also referred to as the terminal). | +| TR-31 | A TR-31 key block is a standardized format used to securely exchange cryptographic keys between systems, especially in financial environments involving HSMs (Hardware Security Modules). It was defined by the ANSI X9.24-1 standard. | +| PGP | Pretty Good Privacy (PGP) is an encryption program that provides cryptographic privacy and authentication for data communication, primarily used for securing emails and files. It combines public-key and symmetric-key cryptography to ensure that messages remain confidential and can be verified for authenticity. | diff --git a/test/hsm-emulator/src/app.ts b/test/hsm-emulator/src/app.ts index 81b6a005eb..0f90bdc356 100644 --- a/test/hsm-emulator/src/app.ts +++ b/test/hsm-emulator/src/app.ts @@ -2,7 +2,8 @@ import fastify from 'fastify' import logger from './logger' import { AES_AUSTRIA_CARD_LMK_HEX, - AES_ILF_LMK_HEX, + AES_CUSTOMER_ASE_LMK_HEX, + AES_MERCHANT_ASE_LMK_HEX, AES_KAI_LMK_HEX, createTR31KeyBlockUnder, importTMK, @@ -20,69 +21,106 @@ import { export function createApp(port: number) { const app = fastify() - app.post('/hsm-ilf/generate-zmk', async function handler(ffReq, ffReply) { - const genCleanZmkKey = generate3DESKeyFromComponents() - const { iv, tr31Block } = createTR31KeyBlockUnder( - Tr31Intent.LMK, - AES_ILF_LMK_HEX, - KeyUsage.ZMK, - 'T', - genCleanZmkKey.finalKeyBuffer - ) + app.post( + '/hsm/ase-customer/generate-zmk', + async function handler(ffReq, ffReply) { + const genCleanZmkKey = generate3DESKeyFromComponents() + const { iv, tr31Block } = createTR31KeyBlockUnder( + Tr31Intent.LMK, + AES_CUSTOMER_ASE_LMK_HEX, + KeyUsage.ZMK, + 'T', + genCleanZmkKey.finalKeyBuffer + ) - logger.info( - `Generated ZMK: ${tr31Block.toString('ascii')} with KCV: ${genCleanZmkKey.kcv}` - ) + logger.info( + `Generated ZMK (Issuer): ${tr31Block.toString('ascii')} with KCV: ${genCleanZmkKey.kcv}` + ) - ffReply.code(200).send({ - iv: iv.toString('hex'), - tr31Block: tr31Block.toString('ascii'), // Only to be used with ILF HSM - component1: genCleanZmkKey.component1, // DANGER! Sent to custodian 1. - component2: genCleanZmkKey.component2, // DANGER! Sent to custodian 2. - component3: genCleanZmkKey.component3, // DANGER! Sent to custodian 3. - finalKey: genCleanZmkKey.finalKey, // FATAL! Never in the clear. XOR of key elements. - kcv: genCleanZmkKey.kcv // This allows all parties to verify the integrity of the key. - }) - }) + ffReply.code(200).send({ + iv: iv.toString('hex'), + tr31Block: tr31Block.toString('ascii'), // Only to be used with ILF HSM + component1: genCleanZmkKey.component1, // DANGER! Sent to custodian 1. + component2: genCleanZmkKey.component2, // DANGER! Sent to custodian 2. + component3: genCleanZmkKey.component3, // DANGER! Sent to custodian 3. + finalKey: genCleanZmkKey.finalKey, // FATAL! Never in the clear. XOR of key elements. + kcv: genCleanZmkKey.kcv // This allows all parties to verify the integrity of the key. + }) + } + ) - app.post('/hsm-ilf/generate-bdk', async function handler(ffReq, ffReply) { - const requestBody = JSON.parse(JSON.stringify(ffReq.body)) - const { zmkUnderLmk } = requestBody + app.post( + '/hsm/ase-merchant/generate-zmk', + async function handler(ffReq, ffReply) { + const genCleanZmkKey = generate3DESKeyFromComponents() + const { iv, tr31Block } = createTR31KeyBlockUnder( + Tr31Intent.LMK, + AES_MERCHANT_ASE_LMK_HEX, + KeyUsage.ZMK, + 'T', + genCleanZmkKey.finalKeyBuffer + ) - const genBdkKey = generateBDK(AES_ILF_LMK_HEX, zmkUnderLmk) + logger.info( + `Generated ZMK (Acquirer): ${tr31Block.toString('ascii')} with KCV: ${genCleanZmkKey.kcv}` + ) - logger.info( - `ILF generated BDK '${genBdkKey.tr31BdkUnderZmk}|${genBdkKey.tr31BdkUnderZmk}' with KCV: ${genBdkKey.kcv}` - ) + ffReply.code(200).send({ + iv: iv.toString('hex'), + tr31Block: tr31Block.toString('ascii'), // Only to be used with ILF HSM + component1: genCleanZmkKey.component1, // DANGER! Sent to custodian 1. + component2: genCleanZmkKey.component2, // DANGER! Sent to custodian 2. + component3: genCleanZmkKey.component3, // DANGER! Sent to custodian 3. + finalKey: genCleanZmkKey.finalKey, // FATAL! Never in the clear. XOR of key elements. + kcv: genCleanZmkKey.kcv // This allows all parties to verify the integrity of the key. + }) + } + ) - ffReply.code(200).send({ - tr31BdkUnderLmk: genBdkKey.tr31BdkUnderLmk, - tr31BdkUnderZmk: genBdkKey.tr31BdkUnderZmk, - kcv: genBdkKey.kcv - }) - }) + app.post( + '/hsm/ase-merchant/generate-bdk', + async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { zmkUnderLmk } = requestBody - app.post('/hsm-ilf/derive-ipek', async function handler(ffReq, ffReply) { - const requestBody = JSON.parse(JSON.stringify(ffReq.body)) - const { tr31BdkUnderLmk, tr31TmkUnderLmk, ksnHex } = requestBody - const ipek = deriveIPEK( - AES_ILF_LMK_HEX, - tr31BdkUnderLmk, - tr31TmkUnderLmk, - ksnHex - ) - logger.info( - `ILF generated IPEK '${ipek.tr31IpekUnderLmk}|${ipek.tr31IpekUnderTmk}' with KCV: ${ipek.kcv}` - ) + const genBdkKey = generateBDK(AES_MERCHANT_ASE_LMK_HEX, zmkUnderLmk) - ffReply.code(200).send({ - tr31IpekUnderLmk: ipek.tr31IpekUnderLmk, - tr31IpekUnderTmk: ipek.tr31IpekUnderTmk, - kcv: ipek.kcv - }) - }) + logger.info( + `ILF generated BDK (Acquirer) '${genBdkKey.tr31BdkUnderZmk}|${genBdkKey.tr31BdkUnderZmk}' with KCV: ${genBdkKey.kcv}` + ) - app.post('/hsm-kai/import-zmk', async function handler(ffReq, ffReply) { + ffReply.code(200).send({ + tr31BdkUnderLmk: genBdkKey.tr31BdkUnderLmk, + tr31BdkUnderZmk: genBdkKey.tr31BdkUnderZmk, + kcv: genBdkKey.kcv + }) + } + ) + + app.post( + '/hsm/ase-merchant/derive-ipek', + async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31BdkUnderLmk, tr31TmkUnderLmk, ksnHex } = requestBody + const ipek = deriveIPEK( + AES_MERCHANT_ASE_LMK_HEX, + tr31BdkUnderLmk, + tr31TmkUnderLmk, + ksnHex + ) + logger.info( + `ILF generated IPEK (Acquirer) '${ipek.tr31IpekUnderLmk}|${ipek.tr31IpekUnderTmk}' with KCV: ${ipek.kcv}` + ) + + ffReply.code(200).send({ + tr31IpekUnderLmk: ipek.tr31IpekUnderLmk, + tr31IpekUnderTmk: ipek.tr31IpekUnderTmk, + kcv: ipek.kcv + }) + } + ) + + app.post('/hsm/kai-os/import-zmk', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { component1, component2, component3, kcv } = requestBody @@ -96,7 +134,7 @@ export function createApp(port: number) { ) logger.info( - `KaiOS imported ZMK '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` + `KaiOS imported ZMK (Terminal Manufacturer) '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` ) ffReply.code(200).send({ @@ -107,7 +145,7 @@ export function createApp(port: number) { }) app.post( - '/hsm-austria-card/import-zmk', + '/hsm/austria-card/import-zmk', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { component1, component2, component3, kcv } = requestBody @@ -122,7 +160,7 @@ export function createApp(port: number) { ) logger.info( - `AustriaCard imported ZMK '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` + `AustriaCard imported ZMK (Card Personalization) '${zmkKey.tr31KeyBlock}' with KCV: ${zmkKey.kcv}` ) ffReply.code(200).send({ @@ -133,14 +171,14 @@ export function createApp(port: number) { } ) - app.post('/hsm-kai/generate-tmk', async function handler(ffReq, ffReply) { + app.post('/hsm/kai-os/generate-tmk', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { zmkUnderLmk, terminalSerial } = requestBody const genTmkKey = generateTMK(AES_KAI_LMK_HEX, zmkUnderLmk) logger.info( - `KaiOS generated TMK '${genTmkKey.tr31TmkUnderLmk}|${genTmkKey.tr31TmkUnderZmk}' with KCV: ${genTmkKey.kcv}` + `KaiOS generated TMK (Terminal Manufacturer) '${genTmkKey.tr31TmkUnderLmk}|${genTmkKey.tr31TmkUnderZmk}' with KCV: ${genTmkKey.kcv}` ) ffReply.code(200).send({ @@ -151,39 +189,45 @@ export function createApp(port: number) { }) }) - app.post('/hsm-ilf/import-tmk', async function handler(ffReq, ffReply) { - const requestBody = JSON.parse(JSON.stringify(ffReq.body)) - const { tr31ZmkUnderLmk, tr31TmkUnderZmk, kcv } = requestBody + app.post( + '/hsm/ase-merchant/import-tmk', + async function handler(ffReq, ffReply) { + const requestBody = JSON.parse(JSON.stringify(ffReq.body)) + const { tr31ZmkUnderLmk, tr31TmkUnderZmk, kcv } = requestBody - const tr31TmkUnderLmk = importTMK( - AES_ILF_LMK_HEX, - tr31ZmkUnderLmk, - tr31TmkUnderZmk, - kcv - ) + const tr31TmkUnderLmk = importTMK( + AES_MERCHANT_ASE_LMK_HEX, + tr31ZmkUnderLmk, + tr31TmkUnderZmk, + kcv + ) - logger.info( - `ILF imported TMK '${tr31TmkUnderLmk.tr31TmkUnderLmk}' with KCV: ${tr31TmkUnderLmk.kcv}` - ) + logger.info( + `ILF imported TMK (Merchant) '${tr31TmkUnderLmk.tr31TmkUnderLmk}' with KCV: ${tr31TmkUnderLmk.kcv}` + ) - ffReply.code(200).send({ - tr31TmkUnderLmk: tr31TmkUnderLmk.tr31TmkUnderLmk, - kcv: tr31TmkUnderLmk.kcv - }) - }) + ffReply.code(200).send({ + tr31TmkUnderLmk: tr31TmkUnderLmk.tr31TmkUnderLmk, + kcv: tr31TmkUnderLmk.kcv + }) + } + ) // At this point, we have a mechanism for transporting keys to the card and terminal via TMK. // We are now able to issue SRED and PIN BDK keys. app.post( - '/hsm-ilf/generate-card-key', + '/hsm/ase-customer/generate-card-key', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { tr31ZmkUnderLmk } = requestBody - const genCardKey = generateCardKey(AES_ILF_LMK_HEX, tr31ZmkUnderLmk) + const genCardKey = generateCardKey( + AES_CUSTOMER_ASE_LMK_HEX, + tr31ZmkUnderLmk + ) logger.info( - `ILF generated Card Key-Pair '${genCardKey.tr31CardKeyUnderLmk}|${genCardKey.tr31CardKeyUnderZmk}' with KCV: ${genCardKey.kcv}` + `ILF generated Card Key-Pair (Issuer) '${genCardKey.tr31CardKeyUnderLmk}|${genCardKey.tr31CardKeyUnderZmk}' with KCV: ${genCardKey.kcv}` ) ffReply.code(200).send({ @@ -196,7 +240,7 @@ export function createApp(port: number) { ) app.post( - '/hsm-austria-card/import-card-key', + '/hsm/austria-card/import-card-key', async function handler(ffReq, ffReply) { const requestBody = JSON.parse(JSON.stringify(ffReq.body)) const { tr31ZmkUnderLmk, tr31CardKeyUnderZmk, kcv } = requestBody @@ -209,7 +253,7 @@ export function createApp(port: number) { ) logger.info( - `ILF imported CardKey '${tr31CardKeyUnderLmk.tr31CardKeyUnderLmk}' with KCV: ${tr31CardKeyUnderLmk.kcv}` + `ILF imported CardKey (Card Personalization) '${tr31CardKeyUnderLmk.tr31CardKeyUnderLmk}' with KCV: ${tr31CardKeyUnderLmk.kcv}` ) ffReply.code(200).send({ diff --git a/test/hsm-emulator/src/card-hsm.ts b/test/hsm-emulator/src/card-hsm.ts index 47e3d2dbcf..08463cbbf6 100644 --- a/test/hsm-emulator/src/card-hsm.ts +++ b/test/hsm-emulator/src/card-hsm.ts @@ -8,10 +8,16 @@ import { import logger from './logger' /** - * The AES Local Master Key for the ILF HSM. + * The AES Local Master Key for the Customer ASE HSM. */ -const AES_ILF_LMK_HEX = +const AES_CUSTOMER_ASE_LMK_HEX = '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' + +/** + * The AES Local Master Key for the Merchant ASE HSM. + */ +const AES_MERCHANT_ASE_LMK_HEX = + '01112233445566778899aabbccddeeff00112233445566778899aabbccddeeff' /** * The AES Local Master Key for the KaiOS HSM. */ @@ -607,7 +613,8 @@ function createTR31KeyBlockUnder( function isKeyUsageForNon3DS(keyUsage: KeyUsage): boolean { switch (keyUsage) { - case (KeyUsage.DEK, KeyUsage.IPK): + case KeyUsage.DEK: + case KeyUsage.IPK: return true default: return false @@ -662,7 +669,8 @@ export { importTMK, generateCardKey, importCardKey, - AES_ILF_LMK_HEX, + AES_MERCHANT_ASE_LMK_HEX, + AES_CUSTOMER_ASE_LMK_HEX, AES_KAI_LMK_HEX, AES_AUSTRIA_CARD_LMK_HEX, KeyUsage, From 7020783ebd2e7baaed258d8abb3a913713b7907e Mon Sep 17 00:00:00 2001 From: koekiebox Date: Tue, 10 Jun 2025 13:37:04 +0200 Subject: [PATCH 12/13] feat(car-6): fix error desc. --- packages/auth/src/signature/middleware.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/auth/src/signature/middleware.ts b/packages/auth/src/signature/middleware.ts index 3980fb2dd9..1b8e63c0c5 100644 --- a/packages/auth/src/signature/middleware.ts +++ b/packages/auth/src/signature/middleware.ts @@ -66,7 +66,7 @@ export async function grantContinueHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'continue: invalid signature headers' + 'invalid signature headers' ) } @@ -119,7 +119,7 @@ export async function grantInitiationHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'initiate: invalid signature headers' + 'invalid signature headers' ) } @@ -144,7 +144,7 @@ export async function tokenHttpsigMiddleware( throw new GNAPServerRouteError( 401, GNAPErrorCode.InvalidClient, - 'token: invalid signature headers' + 'invalid signature headers' ) } From a0deed3971ef4184e52370f67b8a55cc9999da49 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Thu, 3 Jul 2025 12:53:57 +0200 Subject: [PATCH 13/13] feat(car-6): fix conflicts. --- pnpm-lock.yaml | 272 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 253 insertions(+), 19 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62d11c5302..0fb1a08531 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -749,6 +749,28 @@ importers: specifier: ^6.7.6 version: 6.7.6 + test/hsm-emulator: + dependencies: + fastify: + specifier: ^5.2.1 + version: 5.4.0 + pino: + specifier: ^9.6.0 + version: 9.7.0 + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.14.15 + '@types/pg': + specifier: ^8.11.11 + version: 8.15.4 + tsx: + specifier: ^4.19.3 + version: 4.20.3 + typescript: + specifier: ^5.0.0 + version: 5.8.3 + test/integration: devDependencies: '@interledger/open-payments': @@ -4005,11 +4027,46 @@ packages: resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + /@fastify/ajv-compiler@4.0.2: + resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + dev: false + /@fastify/busboy@2.1.1: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} dev: true + /@fastify/error@4.2.0: + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + dev: false + + /@fastify/fast-json-stringify-compiler@5.0.3: + resolution: {integrity: sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==} + dependencies: + fast-json-stringify: 6.0.1 + dev: false + + /@fastify/forwarded@3.0.0: + resolution: {integrity: sha512-kJExsp4JCms7ipzg7SJ3y8DwmePaELHxKYtg+tZow+k0znUTf3cb+npgyqm8+ATZOdmfgfydIebPDWM172wfyA==} + dev: false + + /@fastify/merge-json-schemas@0.2.1: + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + dependencies: + dequal: 2.0.3 + dev: false + + /@fastify/proxy-addr@5.0.0: + resolution: {integrity: sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA==} + dependencies: + '@fastify/forwarded': 3.0.0 + ipaddr.js: 2.2.0 + dev: false + /@graphql-codegen/add@5.0.3(graphql@16.11.0): resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==} peerDependencies: @@ -8308,9 +8365,16 @@ packages: /@types/pg-pool@2.0.4: resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} dependencies: - '@types/pg': 8.6.1 + '@types/pg': 8.15.4 dev: false + /@types/pg@8.15.4: + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} + dependencies: + '@types/node': 20.14.15 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + /@types/pg@8.6.1: resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} dependencies: @@ -8657,7 +8721,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8678,7 +8742,7 @@ packages: debug: 4.4.0(supports-color@9.4.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.6.3 + semver: 7.7.2 tsutils: 3.21.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -8721,7 +8785,7 @@ packages: '@typescript-eslint/typescript-estree': 5.60.1(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -8741,7 +8805,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) eslint: 8.57.1 eslint-scope: 5.1.1 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color - typescript @@ -9122,6 +9186,10 @@ packages: dependencies: event-target-shim: 5.0.1 + /abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + dev: false + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -9210,6 +9278,17 @@ packages: dependencies: ajv: 8.17.1 + /ajv-formats@3.0.1(ajv@8.17.1): + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.17.1 + dev: false + /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -9835,6 +9914,13 @@ packages: dependencies: possible-typed-array-names: 1.0.0 + /avvio@9.1.0: + resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.19.1 + dev: false + /axe-core@4.10.2: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} @@ -12826,7 +12912,6 @@ packages: /fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - dev: true /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -12861,6 +12946,17 @@ packages: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true + /fast-json-stringify@6.0.1: + resolution: {integrity: sha512-s7SJE83QKBZwg54dIbD5rCtzOBVD43V1ReWXXYqBgwCwHLYAAT0RQc/FmrQglXqWPpz6omtryJQOau5jI4Nrvg==} + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-uri: 3.0.3 + json-schema-ref-resolver: 2.0.1 + rfdc: 1.4.1 + dev: false + /fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true @@ -12869,7 +12965,6 @@ packages: resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 - dev: true /fast-redact@3.1.2: resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} @@ -12892,11 +12987,37 @@ packages: engines: {node: '>= 4.9.1'} dev: true + /fastify@5.4.0: + resolution: {integrity: sha512-I4dVlUe+WNQAhKSyv15w+dwUh2EPiEl4X2lGYMmNSgF83WzTMAPKGdWEv5tPsCQOb+SOZwz8Vlta2vF+OeDgRw==} + dependencies: + '@fastify/ajv-compiler': 4.0.2 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.0.3 + '@fastify/proxy-addr': 5.0.0 + abstract-logging: 2.0.1 + avvio: 9.1.0 + fast-json-stringify: 6.0.1 + find-my-way: 9.3.0 + light-my-request: 6.6.0 + pino: 9.7.0 + process-warning: 5.0.0 + rfdc: 1.4.1 + secure-json-parse: 4.0.0 + semver: 7.7.2 + toad-cache: 3.7.0 + dev: false + /fastq@1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: reusify: 1.0.4 + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + dependencies: + reusify: 1.0.4 + dev: false + /fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} dependencies: @@ -12998,6 +13119,15 @@ packages: pkg-dir: 7.0.0 dev: true + /find-my-way@9.3.0: + resolution: {integrity: sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==} + engines: {node: '>=20'} + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.1 + safe-regex2: 5.0.0 + dev: false + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -13308,6 +13438,12 @@ packages: get-intrinsic: 1.2.4 dev: true + /get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /get-tsconfig@4.5.0: resolution: {integrity: sha512-MjhiaIWCJ1sAU4pIQ5i5OfOuHHxVo1oYeNsWTON7jxYkod8pHocXeh+SSbmu5OZZZK73B6cbJ2XADzXehLyovQ==} dev: true @@ -14255,6 +14391,11 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + /ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + dev: false + /iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} dev: false @@ -14767,7 +14908,7 @@ packages: '@babel/parser': 7.26.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -15204,7 +15345,7 @@ packages: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.3 + semver: 7.7.2 transitivePeerDependencies: - supports-color dev: true @@ -15344,6 +15485,12 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dev: true + /json-schema-ref-resolver@2.0.1: + resolution: {integrity: sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==} + dependencies: + dequal: 2.0.3 + dev: false + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true @@ -15634,6 +15781,14 @@ packages: type-check: 0.4.0 dev: true + /light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + dependencies: + cookie: 1.0.2 + process-warning: 4.0.1 + set-cookie-parser: 2.7.1 + dev: false + /lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -17413,7 +17568,7 @@ packages: resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: - semver: 7.6.3 + semver: 7.7.2 dev: true /npm-normalize-package-bin@3.0.1: @@ -17427,7 +17582,7 @@ packages: dependencies: hosted-git-info: 6.1.1 proc-log: 3.0.0 - semver: 7.6.3 + semver: 7.7.2 validate-npm-package-name: 5.0.0 dev: true @@ -17438,7 +17593,7 @@ packages: npm-install-checks: 6.3.0 npm-normalize-package-bin: 3.0.1 npm-package-arg: 10.1.0 - semver: 7.6.3 + semver: 7.7.2 dev: true /npm-run-all2@6.2.6: @@ -18072,7 +18227,6 @@ packages: /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - dev: false /pg-pool@3.6.1(pg@8.11.3): resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} @@ -18084,7 +18238,6 @@ packages: /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -18095,7 +18248,6 @@ packages: postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 - dev: false /pg@8.11.3: resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==} @@ -18152,6 +18304,12 @@ packages: readable-stream: 4.1.0 split2: 4.1.0 + /pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + dependencies: + split2: 4.1.0 + dev: false + /pino-pretty@11.0.0: resolution: {integrity: sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==} hasBin: true @@ -18174,6 +18332,10 @@ packages: /pino-std-serializers@6.0.0: resolution: {integrity: sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==} + /pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + dev: false + /pino@8.19.0: resolution: {integrity: sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==} hasBin: true @@ -18190,6 +18352,23 @@ packages: sonic-boom: 3.7.0 thread-stream: 2.1.0 + /pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.3.1 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + dev: false + /pirates@4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -18401,24 +18580,20 @@ packages: /postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} - dev: false /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} engines: {node: '>=0.10.0'} - dev: false /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} engines: {node: '>=0.10.0'} - dev: false /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 - dev: false /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -18494,6 +18669,14 @@ packages: /process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + /process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + dev: false + + /process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + dev: false + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -19285,6 +19468,10 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve.exports@2.0.2: resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} engines: {node: '>=10'} @@ -19331,6 +19518,11 @@ packages: resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} dev: false + /ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + dev: false + /retext-latin@4.0.0: resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} dependencies: @@ -19382,6 +19574,10 @@ packages: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true + /rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + dev: false + /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -19549,6 +19745,12 @@ packages: is-regex: 1.2.1 dev: true + /safe-regex2@5.0.0: + resolution: {integrity: sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==} + dependencies: + ret: 0.5.0 + dev: false + /safe-stable-stringify@2.3.1: resolution: {integrity: sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg==} engines: {node: '>=10'} @@ -19595,6 +19797,10 @@ packages: /secure-json-parse@2.5.0: resolution: {integrity: sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w==} + /secure-json-parse@4.0.0: + resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==} + dev: false + /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -19994,6 +20200,12 @@ packages: dependencies: atomic-sleep: 1.0.0 + /sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + dependencies: + atomic-sleep: 1.0.0 + dev: false + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -20702,6 +20914,12 @@ packages: dependencies: real-require: 0.2.0 + /thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + dependencies: + real-require: 0.2.0 + dev: false + /through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} dependencies: @@ -20793,6 +21011,11 @@ packages: dependencies: is-number: 7.0.0 + /toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + dev: false + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -20983,6 +21206,17 @@ packages: typescript: 5.8.3 dev: true + /tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.25.2 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /turbo-stream@2.4.0: resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}