diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2469388c..02372cb1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -253,7 +253,9 @@ jobs: ./wait-and-test.sh platform platform-xtest: - needs: platform-roundtrip + needs: + - cli + - lib uses: opentdf/tests/.github/workflows/xtest.yml@main with: js-ref: ${{ github.ref }} diff --git a/.github/workflows/roundtrip/keycloak_data.yaml b/.github/workflows/roundtrip/keycloak_data.yaml new file mode 100644 index 00000000..006a326b --- /dev/null +++ b/.github/workflows/roundtrip/keycloak_data.yaml @@ -0,0 +1,114 @@ +baseUrl: &baseUrl http://localhost:8888 +serverBaseUrl: &serverBaseUrl http://localhost:8080 +customAudMapper: &customAudMapper + name: audience-mapper + protocol: openid-connect + protocolMapper: oidc-audience-mapper + config: + included.custom.audience: *serverBaseUrl + access.token.claim: "true" + id.token.claim: "true" +realms: + - realm_repepresentation: + realm: opentdf + enabled: true + custom_realm_roles: + - name: opentdf-org-admin + - name: opentdf-admin + - name: opentdf-standard + custom_client_roles: + tdf-entity-resolution: + - name: entity-resolution-test-role + custom_groups: + - name: mygroup + attributes: + mygroupattribute: + - mygroupvalue + clients: + - client: + clientID: opentdf + enabled: true + name: opentdf + serviceAccountsEnabled: true + clientAuthenticatorType: client-secret + secret: secret + protocolMappers: + - *customAudMapper + sa_realm_roles: + - opentdf-org-admin + - client: + clientID: opentdf-sdk + enabled: true + name: opentdf-sdk + serviceAccountsEnabled: true + clientAuthenticatorType: client-secret + secret: secret + protocolMappers: + - *customAudMapper + sa_realm_roles: + - opentdf-standard + - client: + clientID: tdf-entity-resolution + enabled: true + name: tdf-entity-resolution + serviceAccountsEnabled: true + clientAuthenticatorType: client-secret + secret: secret + protocolMappers: + - *customAudMapper + sa_client_roles: + realm-management: + - view-clients + - query-clients + - view-users + - query-users + - client: + clientID: tdf-authorization-svc + enabled: true + name: tdf-authorization-svc + serviceAccountsEnabled: true + clientAuthenticatorType: client-secret + secret: secret + protocolMappers: + - *customAudMapper + - client: + clientID: opentdf-public + enabled: true + name: opentdf-public + serviceAccountsEnabled: false + publicClient: true + redirectUris: + - 'http://localhost:9000/*' # otdfctl CLI tool + protocolMappers: + - *customAudMapper + users: + - username: sample-user + enabled: true + firstName: sample + lastName: user + email: sampleuser@sample.com + credentials: + - value: testuser123 + type: password + attributes: + superhero_name: + - thor + superhero_group: + - avengers + groups: + - mygroup + realmRoles: + - opentdf-org-admin + clientRoles: + realm-management: + - view-clients + - query-clients + - view-users + - query-users + tdf-entity-resolution: + - entity-resolution-test-role + token_exchanges: + - start_client: opentdf + target_client: opentdf-sdk + + \ No newline at end of file diff --git a/.github/workflows/roundtrip/opentdf.yaml b/.github/workflows/roundtrip/opentdf.yaml index 124576e3..c5424a62 100644 --- a/.github/workflows/roundtrip/opentdf.yaml +++ b/.github/workflows/roundtrip/opentdf.yaml @@ -10,25 +10,18 @@ logger: # password: changeme services: kas: - enabled: true keyring: - kid: e1 alg: ec:secp256r1 + - kid: e1 + alg: ec:secp256r1 + legacy: true - kid: r1 alg: rsa:2048 - kid: r1 alg: rsa:2048 legacy: true - policy: - enabled: true - authorization: - enabled: true - ersurl: http://localhost:65432/entityresolution/resolve - clientid: tdf-authorization-svc - clientsecret: secret - tokenendpoint: http://localhost:65432/auth/realms/opentdf/protocol/openid-connect/token entityresolution: - enabled: true url: http://localhost:65432/auth clientid: 'tdf-entity-resolution' clientsecret: 'secret' @@ -41,6 +34,7 @@ services: server: auth: enabled: true + public_client_id: 'opentdf-public' audience: 'http://localhost:65432' issuer: http://localhost:65432/auth/realms/opentdf policy: diff --git a/.github/workflows/roundtrip/wait-and-test.sh b/.github/workflows/roundtrip/wait-and-test.sh index 223b12a5..ec87959a 100755 --- a/.github/workflows/roundtrip/wait-and-test.sh +++ b/.github/workflows/roundtrip/wait-and-test.sh @@ -106,7 +106,7 @@ _init_platform() { if [ -f go.work ]; then svc=github.com/opentdf/platform/service fi - if ! go run "${svc}" provision keycloak; then + if ! go run "${svc}" provision keycloak -f "${APP_DIR}/keycloak_data.yaml"; then echo "[ERROR] unable to provision keycloak" return 1 fi diff --git a/Makefile b/Makefile index 409f19db..bbbbb3ac 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,13 @@ version=2.0.0 extras=cli remote-store web-app pkgs=lib $(extras) -.PHONY: all audit license-check lint test ci i start format clean +.PHONY: all audit ci clean cli format i license-check lint start test start: all (cd web-app && npm run dev) +cli: cli/opentdf-cli-$(version).tgz + clean: rm -f *.tgz rm -f */*.tgz diff --git a/cli/package-lock.json b/cli/package-lock.json index fe0b171c..76a0e705 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -39,7 +39,6 @@ "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -372,8 +371,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-0BJzWle4f2xoBKXYmifx0XLmYCn5A60cMuUBNG5inwKaLl+ES8gYgSHjx5ivsKsvv4dA8hW7SUPdjDeuxujchA==", - "license": "BSD-3-Clause-Clear", + "integrity": "sha512-hbqa4laP/qPlrmsVoyNjW5Z55wBkbKXHsqQyyp90FtxmKCrUcuZhnLLQS4cBn7Wpx+8qa67UAcedQ2Xdb7Cn9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -876,14 +874,12 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -894,7 +890,6 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", - "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -923,8 +918,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -963,8 +957,7 @@ "node_modules/browser-fs-access": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", - "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==", - "license": "Apache-2.0" + "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==" }, "node_modules/browser-stdout": { "version": "1.3.1", @@ -990,7 +983,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -1000,7 +992,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", "engines": { "node": "*" } @@ -1144,7 +1135,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1229,7 +1219,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -1271,7 +1260,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -1486,8 +1474,7 @@ "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -1624,7 +1611,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1654,7 +1640,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -1860,8 +1845,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/ignore": { "version": "5.3.1", @@ -1998,7 +1982,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -2046,7 +2029,6 @@ "version": "4.15.9", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -2243,7 +2225,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2252,7 +2233,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2658,8 +2638,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { "version": "2.3.1", @@ -2770,8 +2749,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/require-directory": { "version": "2.1.1", @@ -3055,8 +3033,7 @@ "type": "github", "url": "https://github.com/sponsors/jimmywarting" } - ], - "license": "MIT" + ] }, "node_modules/string-width": { "version": "4.2.3", @@ -3295,7 +3272,6 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } diff --git a/lib/.prettierignore b/lib/.prettierignore new file mode 100644 index 00000000..6bee855e --- /dev/null +++ b/lib/.prettierignore @@ -0,0 +1 @@ +/src/platform \ No newline at end of file diff --git a/lib/src/kas.ts b/lib/src/access.ts similarity index 75% rename from lib/src/kas.ts rename to lib/src/access.ts index 54fef60e..ca8bee01 100644 --- a/lib/src/kas.ts +++ b/lib/src/access.ts @@ -1,4 +1,5 @@ import { type AuthProvider } from './auth/auth.js'; +import { pemToCryptoPublicKey } from './utils.js'; export class RewrapRequest { signedRequestToken = ''; @@ -48,3 +49,14 @@ export async function fetchWrappedKey( return response.json(); } + +export async function fetchECKasPubKey(kasEndpoint: string): Promise { + const kasPubKeyResponse = await fetch(`${kasEndpoint}/kas_public_key?algorithm=ec:secp256r1`); + if (!kasPubKeyResponse.ok) { + throw new Error( + `Unable to validate KAS [${kasEndpoint}]. Received [${kasPubKeyResponse.status}:${kasPubKeyResponse.statusText}]` + ); + } + const pem = await kasPubKeyResponse.json(); + return pemToCryptoPublicKey(pem); +} diff --git a/lib/src/index.ts b/lib/src/index.ts index 18eeddf3..b2c39ca0 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -10,19 +10,8 @@ import { } from './nanotdf/index.js'; import { keyAgreement } from './nanotdf-crypto/index.js'; import { TypedArray, createAttribute, Policy } from './tdf/index.js'; +import { fetchECKasPubKey } from './access.js'; import { ClientConfig } from './nanotdf/Client.js'; -import { pemToCryptoPublicKey } from './utils.js'; - -async function fetchKasPubKey(kasUrl: string): Promise { - const kasPubKeyResponse = await fetch(`${kasUrl}/kas_public_key?algorithm=ec:secp256r1`); - if (!kasPubKeyResponse.ok) { - throw new Error( - `Unable to validate KAS [${kasUrl}]. Received [${kasPubKeyResponse.status}:${kasPubKeyResponse.statusText}]` - ); - } - const pem = await kasPubKeyResponse.json(); - return pemToCryptoPublicKey(pem); -} /** * NanoTDF SDK Client @@ -132,7 +121,7 @@ export class NanoTDFClient extends Client { delete this.iv; if (!this.kasPubKey) { - this.kasPubKey = await fetchKasPubKey(this.kasUrl); + this.kasPubKey = await fetchECKasPubKey(this.kasUrl); } // Create a policy for the tdf @@ -259,7 +248,7 @@ export class NanoTDFDatasetClient extends Client { const ephemeralKeyPair = await this.ephemeralKeyPair; if (!this.kasPubKey) { - this.kasPubKey = await fetchKasPubKey(this.kasUrl); + this.kasPubKey = await fetchECKasPubKey(this.kasUrl); } // Create a policy for the tdf diff --git a/lib/src/nanotdf/Client.ts b/lib/src/nanotdf/Client.ts index 8a5049da..44760a87 100644 --- a/lib/src/nanotdf/Client.ts +++ b/lib/src/nanotdf/Client.ts @@ -3,7 +3,7 @@ import * as base64 from '../encodings/base64.js'; import { generateKeyPair, keyAgreement } from '../nanotdf-crypto/index.js'; import getHkdfSalt from './helpers/getHkdfSalt.js'; import DefaultParams from './models/DefaultParams.js'; -import { fetchWrappedKey } from '../kas.js'; +import { fetchWrappedKey } from '../access.js'; import { AuthProvider, isAuthProvider, reqSignature } from '../auth/providers.js'; import { cryptoPublicToPem, diff --git a/lib/src/tdf/AttributeObject.ts b/lib/src/tdf/AttributeObject.ts index 510b1ce2..3e9b20f3 100644 --- a/lib/src/tdf/AttributeObject.ts +++ b/lib/src/tdf/AttributeObject.ts @@ -17,11 +17,11 @@ export async function createAttribute( kasUrl: string ): Promise { return { - attribute: attribute, + attribute, isDefault: false, displayName: '', pubKey: await cryptoPublicToPem(pubKey), - kasUrl: kasUrl, + kasUrl, schemaVersion: '1.1.0', }; } diff --git a/lib/tdf3/index.ts b/lib/tdf3/index.ts index b906ee7f..bad39aa8 100644 --- a/lib/tdf3/index.ts +++ b/lib/tdf3/index.ts @@ -10,6 +10,7 @@ import { type DecryptKeyMiddleware, type DecryptStreamMiddleware, EncryptParamsBuilder, + type SplitStep, } from './src/client/builders.js'; import { type ClientConfig, createSessionKeys } from './src/client/index.js'; import { @@ -56,6 +57,7 @@ export type { EncryptStreamMiddleware, DecryptKeyMiddleware, DecryptStreamMiddleware, + SplitStep, }; export { diff --git a/lib/tdf3/src/client/builders.ts b/lib/tdf3/src/client/builders.ts index 007d65d1..6efb899d 100644 --- a/lib/tdf3/src/client/builders.ts +++ b/lib/tdf3/src/client/builders.ts @@ -26,6 +26,11 @@ export type EncryptStreamMiddleware = ( stream: DecoratedReadableStream ) => Promise; +export type SplitStep = { + kas: string; + sid?: string; +}; + export type EncryptParams = { source: ReadableStream; opts?: { keypair: PemKeyPair }; @@ -40,6 +45,7 @@ export type EncryptParams = { eo?: EntityObject; payloadKey?: Binary; keyMiddleware?: EncryptKeyMiddleware; + splitPlan?: SplitStep[]; streamMiddleware?: EncryptStreamMiddleware; }; diff --git a/lib/tdf3/src/client/index.ts b/lib/tdf3/src/client/index.ts index 44b53a27..ed65256a 100644 --- a/lib/tdf3/src/client/index.ts +++ b/lib/tdf3/src/client/index.ts @@ -40,6 +40,7 @@ import { DecryptStreamMiddleware, EncryptKeyMiddleware, EncryptStreamMiddleware, + SplitStep, } from './builders.js'; import { DecoratedReadableStream } from './DecoratedReadableStream.js'; @@ -221,7 +222,7 @@ export class Client { */ readonly allowedKases: string[]; - readonly kasPublicKey: Promise; + readonly kasKeys: Record> = {}; readonly easEndpoint?: string; @@ -329,13 +330,11 @@ export class Client { dpopKeys: clientConfig.dpopKeys, }); if (clientConfig.kasPublicKey) { - this.kasPublicKey = Promise.resolve({ + this.kasKeys[this.kasEndpoint] = Promise.resolve({ url: this.kasEndpoint, algorithm: 'rsa:2048', publicKey: clientConfig.kasPublicKey, }); - } else { - this.kasPublicKey = fetchKasPublicKey(this.kasEndpoint); } } @@ -366,9 +365,10 @@ export class Client { eo, keyMiddleware = defaultKeyMiddleware, streamMiddleware = async (stream: DecoratedReadableStream) => stream, + splitPlan, }: EncryptParams): Promise { const dpopKeys = await this.dpopKeys; - const kasPublicKey = await this.kasPublicKey; + const policyObject = asPolicy(scope); validatePolicyObject(policyObject); @@ -384,14 +384,23 @@ export class Client { eo.attributes.forEach((attr) => s.addJwtAttribute(attr)); attributeSet = s; } - encryptionInformation.keyAccess.push( - await buildKeyAccess({ - attributeSet, - type: offline ? 'wrapped' : 'remote', - url: kasPublicKey.url, - kid: kasPublicKey.kid, - publicKey: kasPublicKey.publicKey, - metadata, + + const splits: SplitStep[] = splitPlan || [{ kas: this.kasEndpoint }]; + encryptionInformation.keyAccess = await Promise.all( + splits.map(async ({ kas, sid }) => { + if (!(kas in this.kasKeys)) { + this.kasKeys[kas] = fetchKasPublicKey(kas); + } + const kasPublicKey = await this.kasKeys[kas]; + return buildKeyAccess({ + attributeSet, + type: offline ? 'wrapped' : 'remote', + url: kasPublicKey.url, + kid: kasPublicKey.kid, + publicKey: kasPublicKey.publicKey, + metadata, + sid, + }); }) ); const { keyForEncryption, keyForManifest } = await (keyMiddleware as EncryptKeyMiddleware)(); diff --git a/lib/tdf3/src/models/encryption-information.ts b/lib/tdf3/src/models/encryption-information.ts index 22ad0cc2..700ff73c 100644 --- a/lib/tdf3/src/models/encryption-information.ts +++ b/lib/tdf3/src/models/encryption-information.ts @@ -24,8 +24,10 @@ export type Segment = { readonly encryptedSegmentSize?: number; }; +export type SplitType = 'split'; + export type EncryptionInformation = { - readonly type: string; + readonly type: SplitType; readonly keyAccess: KeyAccessObject[]; readonly integrityInformation: { readonly rootSignature: { @@ -75,19 +77,25 @@ export class SplitKey { } async getKeyAccessObjects(policy: Policy, keyInfo: KeyInfo): Promise { + const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort((a, b) => + a.localeCompare(b) + ); const unwrappedKeySplitBuffers = await keySplit( new Uint8Array(keyInfo.unwrappedKeyBinary.asByteArray()), - this.keyAccess.length, + splitIds.length, this.cryptoService ); + const splitsByName = Object.fromEntries( + splitIds.map((sid, index) => [sid, unwrappedKeySplitBuffers[index]]) + ); const keyAccessObjects = []; - for (let i = 0; i < this.keyAccess.length; i++) { + for (const item of this.keyAccess) { // use the key split to encrypt metadata for each key access object - const unwrappedKeySplitBuffer = unwrappedKeySplitBuffers[i]; + const unwrappedKeySplitBuffer = splitsByName[item.sid]; const unwrappedKeySplitBinary = Binary.fromArrayBuffer(unwrappedKeySplitBuffer.buffer); - const metadata = this.keyAccess[i].metadata || ''; + const metadata = item.metadata || ''; const metadataStr = ( typeof metadata === 'object' ? JSON.stringify(metadata) @@ -112,7 +120,7 @@ export class SplitKey { }; const encryptedMetadataStr = JSON.stringify(encryptedMetadataOb); - const keyAccessObject = await this.keyAccess[i].write( + const keyAccessObject = await item.write( policy, unwrappedKeySplitBuffer, encryptedMetadataStr diff --git a/lib/tdf3/src/models/key-access.ts b/lib/tdf3/src/models/key-access.ts index 951c7ddf..efc8b1da 100644 --- a/lib/tdf3/src/models/key-access.ts +++ b/lib/tdf3/src/models/key-access.ts @@ -17,7 +17,8 @@ export class Wrapped { public readonly url: string, public readonly kid: string | undefined, public readonly publicKey: string, - public readonly metadata: unknown + public readonly metadata: unknown, + public readonly sid: string ) {} async write( @@ -51,6 +52,9 @@ export class Wrapped { if (this.kid) { this.keyAccessObject.kid = this.kid; } + if (this.sid?.length) { + this.keyAccessObject.sid = this.sid; + } return this.keyAccessObject; } @@ -66,7 +70,8 @@ export class Remote { public readonly url: string, public readonly kid: string | undefined, public readonly publicKey: string, - public readonly metadata: unknown + public readonly metadata: unknown, + public readonly sid: string ) {} async write( @@ -109,6 +114,7 @@ export class Remote { export type KeyAccess = Remote | Wrapped; export type KeyAccessObject = { + sid?: string; type: KeyAccessType; url: string; kid?: string; diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 0db40f71..acdad7d8 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -18,6 +18,8 @@ import { UpsertResponse, Wrapped as KeyAccessWrapped, KeyAccess, + KeyAccessObject, + SplitType, } from './models/index.js'; import { base64 } from '../../src/encodings/index.js'; import { @@ -70,7 +72,7 @@ export type EncryptionOptions = { /** * Defaults to `split`, the currently only implmented key wrap algorithm. */ - type?: string; + type?: SplitType; // Defaults to AES-256-GCM for the encryption. cipher?: string; }; @@ -92,6 +94,7 @@ export type BuildKeyAccess = { publicKey: string; attributeUrl?: string; metadata?: Metadata; + sid?: string; }; type Segment = { @@ -341,6 +344,7 @@ export async function buildKeyAccess({ kid, attributeUrl, metadata, + sid = '', }: BuildKeyAccess): Promise { /** Internal function to keep it DRY */ function createKeyAccess( @@ -352,9 +356,9 @@ export async function buildKeyAccess({ ) { switch (type) { case 'wrapped': - return new KeyAccessWrapped(kasUrl, kasKeyIdentifier, pubKey, metadata); + return new KeyAccessWrapped(kasUrl, kasKeyIdentifier, pubKey, metadata, sid); case 'remote': - return new KeyAccessRemote(kasUrl, kasKeyIdentifier, pubKey, metadata); + return new KeyAccessRemote(kasUrl, kasKeyIdentifier, pubKey, metadata, sid); default: throw new KeyAccessError(`buildKeyAccess: Key access type ${type} is unknown`); } @@ -801,6 +805,40 @@ async function loadTDFStream( return { manifest, zipReader, centralDirectory }; } +export function splitLookupTableFactory( + keyAccess: KeyAccessObject[], + allowedKases: string[] +): Record> { + const origin = (u: string): string => new URL(u).origin; + const allowed = (k: KeyAccessObject) => allowedKases.includes(origin(k.url)); + const splitIds = new Set(keyAccess.map(({ sid }) => sid ?? '')); + + const accessibleSplits = new Set(keyAccess.filter(allowed).map(({ sid }) => sid)); + if (splitIds.size > accessibleSplits.size) { + const disallowedKases = new Set(keyAccess.filter((k) => !allowed(k)).map(({ url }) => url)); + throw new KasDecryptError( + `Unreconstructable key - disallowed KASes include: ${JSON.stringify([ + ...disallowedKases, + ])} from splitIds ${JSON.stringify([...splitIds])}` + ); + } + const splitPotentials: Record> = Object.fromEntries( + [...splitIds].map((s) => [s, {}]) + ); + for (const kao of keyAccess) { + const disjunction = splitPotentials[kao.sid ?? '']; + if (kao.url in disjunction) { + throw new KasDecryptError( + `TODO: Fallback to no split ids. Repetition found for [${kao.url}] on split [${kao.sid}]` + ); + } + if (allowed(kao)) { + disjunction[kao.url] = kao; + } + } + return splitPotentials; +} + async function unwrapKey({ manifest, allowedKases, @@ -817,23 +855,25 @@ async function unwrapKey({ cryptoService: CryptoService; }) { if (authProvider === undefined) { - throw new Error('Upsert can be done without auth provider'); + throw new KasDecryptError('Upsert can be done without auth provider'); } const { keyAccess } = manifest.encryptionInformation; + const splitPotentials = splitLookupTableFactory(keyAccess, allowedKases); + let responseMetadata; const isAppIdProvider = authProvider && isAppIdProviderCheck(authProvider); // Get key access information to know the KAS URLS const rewrappedKeys = await Promise.all( - keyAccess.map(async (keySplitInfo) => { - const kaoOrigin = new URL(keySplitInfo.url).origin; - if (!allowedKases.includes(kaoOrigin)) { + Object.entries(splitPotentials).map(async ([splitId, potentials]) => { + if (!potentials || !Object.keys(potentials).length) { throw new UnsafeUrlError( - `cannot decrypt TDF: [${keySplitInfo.url}] not on allowlist ${JSON.stringify( - allowedKases - )}`, - keySplitInfo.url + `Unreconstructable key - no valid KAS found for split ${JSON.stringify(splitId)}`, + '' ); } + // If we have multiple ways of getting a value, try the 'best' way + // or maybe retry across all potential ways? Currently, just tries them all + const [keySplitInfo] = Object.values(potentials); const url = `${keySplitInfo.url}/${isAppIdProvider ? '' : 'v2/'}rewrap`; const ephemeralEncryptionKeys = await cryptoService.cryptoToPemPair( diff --git a/lib/tests/mocha/unit/tdf.spec.ts b/lib/tests/mocha/unit/tdf.spec.ts index fe989825..64f9861e 100644 --- a/lib/tests/mocha/unit/tdf.spec.ts +++ b/lib/tests/mocha/unit/tdf.spec.ts @@ -1,7 +1,8 @@ import { expect } from 'chai'; import * as TDF from '../../../tdf3/src/tdf.js'; -import { TdfError } from '../../../src/errors.js'; +import { KasDecryptError, TdfError } from '../../../src/errors.js'; +import { KeyAccessObject } from 'tdf3/src/models/key-access.js'; const sampleCert = ` -----BEGIN CERTIFICATE----- @@ -92,3 +93,80 @@ describe('fetchKasPublicKey', async () => { expect(pk2.kid).to.equal('kid-a'); }); }); + +describe('splitLookupTableFactory', () => { + it('should return a correct split table for valid input', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'https://kas2', protocol: 'kas' }, + ]; + const allowedKases = ['https://kas1', 'https://kas2']; + + const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); + + expect(result).to.deep.equal({ + split1: { 'https://kas1': keyAccess[0] }, + split2: { 'https://kas2': keyAccess[1] }, + }); + }); + + it('should throw KasDecryptError for disallowed KASes', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'https://kas3', protocol: 'kas' }, // kas3 is not allowed + ]; + const allowedKases = ['https://kas1']; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)).to.throw( + KasDecryptError, + 'Unreconstructable key - disallowed KASes include: ["https://kas3"] from splitIds ["split1","split2"]' + ); + }); + + it('should throw KasDecryptError for duplicate URLs in the same splitId', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, // duplicate URL in same splitId + ]; + const allowedKases = ['https://kas1']; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)).to.throw( + KasDecryptError, + 'TODO: Fallback to no split ids. Repetition found for [https://kas1] on split [split1]' + ); + }); + + it('should handle empty keyAccess array', () => { + const keyAccess: KeyAccessObject[] = []; + const allowedKases: string[] = []; + + const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); + + expect(result).to.deep.equal({}); + }); + + it('should handle empty allowedKases array', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + ]; + const allowedKases: string[] = []; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)).to.throw( + KasDecryptError, + 'Unreconstructable key - disallowed KASes include: ["https://kas1"]' + ); + }); + + it('should handle cases where sid is undefined', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: undefined, type: 'remote', url: 'https://kas1', protocol: 'kas' }, + ]; + const allowedKases = ['https://kas1']; + + const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); + + expect(result).to.deep.equal({ + '': { 'https://kas1': keyAccess[0] }, + }); + }); +}); diff --git a/remote-store/package-lock.json b/remote-store/package-lock.json index cf07ea14..8fd068bd 100644 --- a/remote-store/package-lock.json +++ b/remote-store/package-lock.json @@ -1539,7 +1539,6 @@ "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1821,8 +1820,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-0BJzWle4f2xoBKXYmifx0XLmYCn5A60cMuUBNG5inwKaLl+ES8gYgSHjx5ivsKsvv4dA8hW7SUPdjDeuxujchA==", - "license": "BSD-3-Clause-Clear", + "integrity": "sha512-hbqa4laP/qPlrmsVoyNjW5Z55wBkbKXHsqQyyp90FtxmKCrUcuZhnLLQS4cBn7Wpx+8qa67UAcedQ2Xdb7Cn9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -3302,7 +3300,6 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", - "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -3331,8 +3328,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -3373,8 +3369,7 @@ "node_modules/browser-fs-access": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", - "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==", - "license": "Apache-2.0" + "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==" }, "node_modules/browser-stdout": { "version": "1.3.1", @@ -3400,7 +3395,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -3410,7 +3404,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", "engines": { "node": "*" } @@ -3644,7 +3637,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -3872,8 +3864,7 @@ "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -4250,8 +4241,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/ignore": { "version": "5.2.4", @@ -4388,7 +4378,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -4436,7 +4425,6 @@ "version": "4.15.9", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -5196,8 +5184,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/require-directory": { "version": "2.1.1", @@ -5441,8 +5428,7 @@ "type": "github", "url": "https://github.com/sponsors/jimmywarting" } - ], - "license": "MIT" + ] }, "node_modules/string-width": { "version": "4.2.3", diff --git a/web-app/package-lock.json b/web-app/package-lock.json index c351d80a..5c0280da 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -353,7 +353,6 @@ "version": "7.25.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", - "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -607,8 +606,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-0BJzWle4f2xoBKXYmifx0XLmYCn5A60cMuUBNG5inwKaLl+ES8gYgSHjx5ivsKsvv4dA8hW7SUPdjDeuxujchA==", - "license": "BSD-3-Clause-Clear", + "integrity": "sha512-hbqa4laP/qPlrmsVoyNjW5Z55wBkbKXHsqQyyp90FtxmKCrUcuZhnLLQS4cBn7Wpx+8qa67UAcedQ2Xdb7Cn9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -1186,14 +1184,12 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1204,7 +1200,6 @@ "version": "3.9.1", "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", - "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" @@ -1258,8 +1253,7 @@ "node_modules/browser-fs-access": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", - "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==", - "license": "Apache-2.0" + "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==" }, "node_modules/browserslist": { "version": "4.21.10", @@ -1318,7 +1312,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", "engines": { "node": "*" } @@ -1420,7 +1413,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1500,7 +1492,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -1548,7 +1539,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -1851,8 +1841,7 @@ "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -1996,7 +1985,6 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], - "license": "MIT", "engines": { "node": ">=4.0" }, @@ -2010,7 +1998,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2264,7 +2251,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -2281,7 +2267,6 @@ "version": "4.15.9", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -2485,7 +2470,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2494,7 +2478,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2926,8 +2909,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { "version": "2.3.0", @@ -3039,8 +3021,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/resolve": { "version": "1.22.1", @@ -3281,8 +3262,7 @@ "type": "github", "url": "https://github.com/sponsors/jimmywarting" } - ], - "license": "MIT" + ] }, "node_modules/strip-ansi": { "version": "6.0.1", @@ -3510,7 +3490,6 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], - "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -4111,7 +4090,7 @@ }, "@opentdf/client": { "version": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-0BJzWle4f2xoBKXYmifx0XLmYCn5A60cMuUBNG5inwKaLl+ES8gYgSHjx5ivsKsvv4dA8hW7SUPdjDeuxujchA==", + "integrity": "sha512-hbqa4laP/qPlrmsVoyNjW5Z55wBkbKXHsqQyyp90FtxmKCrUcuZhnLLQS4cBn7Wpx+8qa67UAcedQ2Xdb7Cn9w==", "requires": { "axios": "^1.6.1", "axios-retry": "^3.9.0",