From ca927809d9e53913cbadcb6e4546f983735326ed Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 29 Nov 2023 10:33:09 -0500 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=8E=A8=20Move=20KAS=20methods=20in?= =?UTF-8?q?=20nano=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Small refactor to consolidate KAS methods --- lib/src/{kas.ts => access.ts} | 11 +++++++++++ lib/src/index.ts | 6 ++++-- lib/src/nanotdf/Client.ts | 2 +- lib/src/tdf/AttributeObject.ts | 4 ++-- 4 files changed, 18 insertions(+), 5 deletions(-) rename lib/src/{kas.ts => access.ts} (79%) diff --git a/lib/src/kas.ts b/lib/src/access.ts similarity index 79% rename from lib/src/kas.ts rename to lib/src/access.ts index 54fef60e..4a1ece83 100644 --- a/lib/src/kas.ts +++ b/lib/src/access.ts @@ -48,3 +48,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}]` + ); + } + return kasPubKeyResponse.json(); +} + diff --git a/lib/src/index.ts b/lib/src/index.ts index 18eeddf3..e237e920 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -10,6 +10,8 @@ import { } from './nanotdf/index.js'; import { keyAgreement } from './nanotdf-crypto/index.js'; import { TypedArray, createAttribute, Policy } from './tdf/index.js'; +import { AuthProvider } from './auth/auth.js'; +import { fetchECKasPubKey } from './access.js'; import { ClientConfig } from './nanotdf/Client.js'; import { pemToCryptoPublicKey } from './utils.js'; @@ -132,7 +134,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 +261,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', }; } From c4e47f4d588c03eb9a053a1423a2dcf0ef73ba92 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 29 Nov 2023 10:35:46 -0500 Subject: [PATCH 02/15] =?UTF-8?q?=E2=9C=A8=20multikas=20v2:=20splitPlans?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds the ability to share and split DEKs - Reconstructs keys using share ids - Does NOT support TDFs that were created with id-less splits. While the old code did support reading them, it did not support creating them --- lib/src/access.ts | 7 +- lib/src/index.ts | 13 ---- lib/tdf3/index.ts | 2 + lib/tdf3/src/client/builders.ts | 6 ++ lib/tdf3/src/client/index.ts | 38 +++++++---- lib/tdf3/src/models/encryption-information.ts | 18 ++++-- lib/tdf3/src/models/key-access.ts | 10 ++- lib/tdf3/src/tdf.ts | 64 +++++++++++++++---- 8 files changed, 110 insertions(+), 48 deletions(-) diff --git a/lib/src/access.ts b/lib/src/access.ts index 4a1ece83..ca8bee01 100644 --- a/lib/src/access.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 = ''; @@ -49,13 +50,13 @@ export async function fetchWrappedKey( return response.json(); } -export async function fetchECKasPubKey(kasEndpoint: string): Promise { +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}]` ); } - return kasPubKeyResponse.json(); + const pem = await kasPubKeyResponse.json(); + return pemToCryptoPublicKey(pem); } - diff --git a/lib/src/index.ts b/lib/src/index.ts index e237e920..b2c39ca0 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -10,21 +10,8 @@ import { } from './nanotdf/index.js'; import { keyAgreement } from './nanotdf-crypto/index.js'; import { TypedArray, createAttribute, Policy } from './tdf/index.js'; -import { AuthProvider } from './auth/auth.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 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 bcfc5f07..5d0b7fe1 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; @@ -328,13 +329,17 @@ 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); + } + for (const kasEndpoint of this.allowedKases) { + if (kasEndpoint in this.kasKeys) { + continue; + } + this.kasKeys[kasEndpoint] = fetchKasPublicKey(this.kasEndpoint); } } @@ -365,9 +370,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); @@ -383,14 +389,20 @@ 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 }) => { + 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..a62df9df 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,23 @@ export class SplitKey { } async getKeyAccessObjects(policy: Policy, keyInfo: KeyInfo): Promise { + const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort(); 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 +118,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 a41d5638..2c6ca89d 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 = { @@ -340,6 +343,7 @@ export async function buildKeyAccess({ kid, attributeUrl, metadata, + sid = '', }: BuildKeyAccess): Promise { /** Internal function to keep it DRY */ function createKeyAccess( @@ -351,9 +355,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`); } @@ -800,6 +804,41 @@ async function loadTDFStream( return { manifest, zipReader, centralDirectory }; } +export function splitLookupTableFactory( + keyAccess: KeyAccessObject[], + allowedKases: string[] +): Record> { + const splitIds = new Set(keyAccess.map(({ sid }) => sid || '')); + const accessibleSplits = new Set( + keyAccess.filter(({ url }) => allowedKases.includes(url)).map(({ sid }) => sid) + ); + if (splitIds.size > accessibleSplits.size) { + const disallowedKases = new Set( + keyAccess.filter(({ url }) => !allowedKases.includes(url)).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 (allowedKases.includes(kao.url)) { + disjunction[kao.url] = kao; + } + } + return splitPotentials; +} + async function unwrapKey({ manifest, allowedKases, @@ -816,22 +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) => { - if (!allowedKases.includes(keySplitInfo.url)) { + 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)}`, + "" + ); } + // TODO: If we have multiple ways of getting a value, try the 'best' way + // or maybe retry across all potential ways + const [keySplitInfo] = Object.values(potentials); const url = `${keySplitInfo.url}/${isAppIdProvider ? '' : 'v2/'}rewrap`; const ephemeralEncryptionKeys = await cryptoService.cryptoToPemPair( From 6180784cc35454fcfd2649676f999b064d567653 Mon Sep 17 00:00:00 2001 From: dmihalcik-virtru Date: Wed, 31 Jul 2024 16:57:02 +0000 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=A4=96=20=F0=9F=8E=A8=20Autoformat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: David Mihalcik --- lib/tdf3/src/tdf.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 2c6ca89d..82456adc 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -867,9 +867,9 @@ async function unwrapKey({ Object.entries(splitPotentials).map(async ([splitId, potentials]) => { if (!potentials || !Object.keys(potentials).length) { throw new UnsafeUrlError( - `Unreconstructable key - no valid KAS found for split ${JSON.stringify(splitId)}`, - "" - ); + `Unreconstructable key - no valid KAS found for split ${JSON.stringify(splitId)}`, + '' + ); } // TODO: If we have multiple ways of getting a value, try the 'best' way // or maybe retry across all potential ways From cf62c77bd23d9fdb907b63cab0ec0b4c19dffe81 Mon Sep 17 00:00:00 2001 From: Patrick Bacon-Blaber Date: Mon, 19 Aug 2024 10:59:42 -0400 Subject: [PATCH 04/15] add tests to splitLookupTableFactory --- lib/tdf3/src/tdf.ts | 6 +-- lib/tests/mocha/unit/tdf.spec.ts | 74 +++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 82456adc..694035a7 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -817,9 +817,9 @@ export function splitLookupTableFactory( keyAccess.filter(({ url }) => !allowedKases.includes(url)).map(({ url }) => url) ); throw new KasDecryptError( - `Unreconstructable key - disallowed KASes include: [${JSON.stringify( - disallowedKases - )}] from splitIds [${JSON.stringify(splitIds)}]` + `Unreconstructable key - disallowed KASes include: ${JSON.stringify( + [...disallowedKases] + )} from splitIds ${JSON.stringify([...splitIds])}` ); } const splitPotentials: Record> = Object.fromEntries( diff --git a/lib/tests/mocha/unit/tdf.spec.ts b/lib/tests/mocha/unit/tdf.spec.ts index fe989825..8be550b2 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,74 @@ 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: 'kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'kas2', protocol: 'kas' } + ]; + const allowedKases = ['kas1', 'kas2']; + + const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); + + expect(result).to.deep.equal({ + 'split1': { 'kas1': keyAccess[0] }, + 'split2': { 'kas2': keyAccess[1] } + }); + }); + + it('should throw KasDecryptError for disallowed KASes', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'kas3', protocol: 'kas' } // kas3 is not allowed + ]; + const allowedKases = ['kas1']; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)) + .to.throw(KasDecryptError, 'Unreconstructable key - disallowed KASes include: \["kas3"\] from splitIds \["split1","split2"\]'); + }); + + it('should throw KasDecryptError for duplicate URLs in the same splitId', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' }, + { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' } // duplicate URL in same splitId + ]; + const allowedKases = ['kas1']; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)) + .to.throw(KasDecryptError, 'TODO: Fallback to no split ids. Repetition found for \[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: 'kas1', protocol: 'kas' } + ]; + const allowedKases: string[] = []; + + expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)) + .to.throw(KasDecryptError, 'Unreconstructable key - disallowed KASes include: \["kas1"\]'); + }); + + it('should handle cases where sid is undefined', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: undefined, type: 'remote', url: 'kas1', protocol: 'kas' } + ]; + const allowedKases = ['kas1']; + + const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); + + expect(result).to.deep.equal({ + '': { 'kas1': keyAccess[0] } + }); + }); +}); From 82f3d237fc402f6879353d11d560aa8bf9274278 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 14:13:20 -0500 Subject: [PATCH 05/15] adds back origin check dropped in automerge --- cli/sample.tdf | Bin 0 -> 1924 bytes cli/sample.txt | 1 + lib/tdf3/src/tdf.ts | 3 ++- 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 cli/sample.tdf create mode 100644 cli/sample.txt diff --git a/cli/sample.tdf b/cli/sample.tdf new file mode 100644 index 0000000000000000000000000000000000000000..9ed727421de4e58d668f24850498519c9e127b8f GIT binary patch literal 1924 zcmaJ?&9B=;5KmkBTDTx2Kpf!WQ>DDV*v|Kp3UU(1FCXi~juXeOI5_sc_}h-X`LIhx z5mJvxzy)zZLL7SFh{TCAH~0?_DiZ$%D(jc{>9w@%!81EMGy9wI%+7dl@6|=q`B_V8CnWLkn802$O)TyC=RxFY8GGgGICL$9eL65kZHP;McblM;rWXaLu=TYtj2jc-uvIy6Gzg;SpM$VQIc-HF# z-GR~VQ$q$$Cfgdtazikl*2iSO**r?7ILTOQJ2HRbz8iuqGb`A3=0>n{Yg5`BcZUe}nEGs9FHXgJ ze^}A&vvtczrI?*G&_txCyJ!Z@674vP8SnPn8#DxM(`nGjZZ~WXXsV^u){XT-(sXHi z?;cM4;SWOtK%^NO(r~(A(zmsFD^TZsQk|P*&kihp5?6Jpx07Yj(!53lIAkwnXQzk-I*)+tz^oDqXn%u$CF;%^IZCs_Ex`{{rkYR^rU6^hUG7_;?i79h_eY@ zt>I%+i%q;$(yj+2P*r4*Dw1ANJ=Gj_M3=|>sMDhW*GmCCrBunVRF9=b(z47;fK;Te zJ(Bx2NS(4GiI&=7t0QUV0a2ay@zez>?ih%97!_)dnM+{Ga7a&3Tt zSn&i!Q1i5fi`#DBIo1ZNRg29PO)1904vM5zYGs{c`3NqtpX7zciGl!4tfjA^b%edV z7;J21p)0ns<0Yn|c4j7@y56YMwG?kkDzrfI+>`_~pnyEB0&eE;H18^sQdUyY;ypIu zX(3f5h2|yE1f6N3cuJK|g{`{GV#U1|5=09h_h6S9STcmyTpm9!YEkbPO9vN>)n;7ZWSRkx}%O;+pI)-Jev*EaO*{JMdgv zy9nEml}5E*d%59rS~aqQW7$^@P8hCO%8*?rt2D)CDAfarYWEQ6V32K+PgSq;^a0RIO)}}A4i#srZ5*t-E%EtF1lEwCQ~G?WHHpsyt}}9 zGDS7dg^S|Jp8T^X(Ig+p3<$tD1jxDF3A1ua$qod;kpJC3|^#haB9}oM@(VoF}w=#~n_!bJ+ QFh3sLyg1E)WwcQE7ZXBL9RL6T literal 0 HcmV?d00001 diff --git a/cli/sample.txt b/cli/sample.txt new file mode 100644 index 00000000..6b820fd9 --- /dev/null +++ b/cli/sample.txt @@ -0,0 +1 @@ +hello-world diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index fc29c6b3..b259aab8 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -810,8 +810,9 @@ export function splitLookupTableFactory( allowedKases: string[] ): Record> { const splitIds = new Set(keyAccess.map(({ sid }) => sid || '')); + const accessibleSplits = new Set( - keyAccess.filter(({ url }) => allowedKases.includes(url)).map(({ sid }) => sid) + keyAccess.filter(({ url }) => allowedKases.includes(new URL(url).origin)).map(({ sid }) => sid) ); if (splitIds.size > accessibleSplits.size) { const disallowedKases = new Set( From 2a6cd91cfd0a0dfb26ed0c50f136dbc26628a7a6 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 14:22:13 -0500 Subject: [PATCH 06/15] fixups --- cli/package-lock.json | 8 ++--- lib/tdf3/src/tdf.ts | 6 ++-- lib/tests/mocha/unit/tdf.spec.ts | 48 ++++++++++++++++------------- remote-store/package-lock.json | 20 ++++++------ web-app/package-lock.json | 52 ++++++++++++++++---------------- 5 files changed, 70 insertions(+), 64 deletions(-) diff --git a/cli/package-lock.json b/cli/package-lock.json index b78af2d8..ff0e62ac 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -371,7 +371,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-C+R3AaydaZH21ODyr/DiMtmydWsyqjurecc8+Ai8jRwVyn3Cel+jGinu7rda1rPIBx1mOgkDg7HYdt3QKcZ8SA==", + "integrity": "sha512-s9gC2paaov5D+au4yLVnYHG5iXtkcKXnvgjbf7Nubsc03efigjnZHVT0pQMYPW3TvpkeTTDv1OQsXtEmrJXr9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -877,9 +877,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", - "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index b259aab8..ab5cbb4e 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -819,9 +819,9 @@ export function splitLookupTableFactory( keyAccess.filter(({ url }) => !allowedKases.includes(url)).map(({ url }) => url) ); throw new KasDecryptError( - `Unreconstructable key - disallowed KASes include: ${JSON.stringify( - [...disallowedKases] - )} from splitIds ${JSON.stringify([...splitIds])}` + `Unreconstructable key - disallowed KASes include: ${JSON.stringify([ + ...disallowedKases, + ])} from splitIds ${JSON.stringify([...splitIds])}` ); } const splitPotentials: Record> = Object.fromEntries( diff --git a/lib/tests/mocha/unit/tdf.spec.ts b/lib/tests/mocha/unit/tdf.spec.ts index 8be550b2..64f9861e 100644 --- a/lib/tests/mocha/unit/tdf.spec.ts +++ b/lib/tests/mocha/unit/tdf.spec.ts @@ -97,39 +97,43 @@ describe('fetchKasPublicKey', async () => { describe('splitLookupTableFactory', () => { it('should return a correct split table for valid input', () => { const keyAccess: KeyAccessObject[] = [ - { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' }, - { sid: 'split2', type: 'remote', url: 'kas2', protocol: 'kas' } + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'https://kas2', protocol: 'kas' }, ]; - const allowedKases = ['kas1', 'kas2']; + const allowedKases = ['https://kas1', 'https://kas2']; const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); expect(result).to.deep.equal({ - 'split1': { 'kas1': keyAccess[0] }, - 'split2': { 'kas2': keyAccess[1] } + 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: 'kas1', protocol: 'kas' }, - { sid: 'split2', type: 'remote', url: 'kas3', protocol: 'kas' } // kas3 is not allowed + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'https://kas3', protocol: 'kas' }, // kas3 is not allowed ]; - const allowedKases = ['kas1']; + const allowedKases = ['https://kas1']; - expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)) - .to.throw(KasDecryptError, 'Unreconstructable key - disallowed KASes include: \["kas3"\] from splitIds \["split1","split2"\]'); + 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: 'kas1', protocol: 'kas' }, - { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' } // duplicate URL in same splitId + { 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 = ['kas1']; + const allowedKases = ['https://kas1']; - expect(() => TDF.splitLookupTableFactory(keyAccess, allowedKases)) - .to.throw(KasDecryptError, 'TODO: Fallback to no split ids. Repetition found for \[kas1\] on split \[split1\]'); + 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', () => { @@ -143,24 +147,26 @@ describe('splitLookupTableFactory', () => { it('should handle empty allowedKases array', () => { const keyAccess: KeyAccessObject[] = [ - { sid: 'split1', type: 'remote', url: 'kas1', protocol: 'kas' } + { 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: \["kas1"\]'); + 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: 'kas1', protocol: 'kas' } + { sid: undefined, type: 'remote', url: 'https://kas1', protocol: 'kas' }, ]; - const allowedKases = ['kas1']; + const allowedKases = ['https://kas1']; const result = TDF.splitLookupTableFactory(keyAccess, allowedKases); expect(result).to.deep.equal({ - '': { 'kas1': keyAccess[0] } + '': { 'https://kas1': keyAccess[0] }, }); }); }); diff --git a/remote-store/package-lock.json b/remote-store/package-lock.json index bee04472..b9e4ff3b 100644 --- a/remote-store/package-lock.json +++ b/remote-store/package-lock.json @@ -1536,9 +1536,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1820,7 +1820,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-9vExrKuqDY1CHmyy2Z7umlwZRFSI08JjkStzkkTrKRF91hEcBEHRvthuFIplNyX5NRZ85i8AXJCcCoDZLgshsg==", + "integrity": "sha512-s9gC2paaov5D+au4yLVnYHG5iXtkcKXnvgjbf7Nubsc03efigjnZHVT0pQMYPW3TvpkeTTDv1OQsXtEmrJXr9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -3633,9 +3633,9 @@ } }, "node_modules/dpop": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.0.tgz", - "integrity": "sha512-r//f6g3uaDdomkz+3M0NdcEAxSGUVMIhF7lxf06aJZjCFhkH//GeZo6JWpz7Uv025s1w/+M5RDNRGxAGNIzm0g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", + "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -4421,9 +4421,9 @@ } }, "node_modules/jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", "funding": { "url": "https://github.com/sponsors/panva" } diff --git a/web-app/package-lock.json b/web-app/package-lock.json index 4e14e0cb..9182df09 100644 --- a/web-app/package-lock.json +++ b/web-app/package-lock.json @@ -350,9 +350,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -606,7 +606,7 @@ "node_modules/@opentdf/client": { "version": "2.0.0", "resolved": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-9vExrKuqDY1CHmyy2Z7umlwZRFSI08JjkStzkkTrKRF91hEcBEHRvthuFIplNyX5NRZ85i8AXJCcCoDZLgshsg==", + "integrity": "sha512-s9gC2paaov5D+au4yLVnYHG5iXtkcKXnvgjbf7Nubsc03efigjnZHVT0pQMYPW3TvpkeTTDv1OQsXtEmrJXr9w==", "dependencies": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -1187,9 +1187,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1534,9 +1534,9 @@ } }, "node_modules/dpop": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.0.tgz", - "integrity": "sha512-r//f6g3uaDdomkz+3M0NdcEAxSGUVMIhF7lxf06aJZjCFhkH//GeZo6JWpz7Uv025s1w/+M5RDNRGxAGNIzm0g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", + "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -2258,9 +2258,9 @@ "license": "ISC" }, "node_modules/jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -3921,9 +3921,9 @@ } }, "@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", "requires": { "regenerator-runtime": "^0.14.0" } @@ -4081,7 +4081,7 @@ }, "@opentdf/client": { "version": "file:../lib/opentdf-client-2.0.0.tgz", - "integrity": "sha512-9vExrKuqDY1CHmyy2Z7umlwZRFSI08JjkStzkkTrKRF91hEcBEHRvthuFIplNyX5NRZ85i8AXJCcCoDZLgshsg==", + "integrity": "sha512-s9gC2paaov5D+au4yLVnYHG5iXtkcKXnvgjbf7Nubsc03efigjnZHVT0pQMYPW3TvpkeTTDv1OQsXtEmrJXr9w==", "requires": { "axios": "^1.6.1", "axios-retry": "^3.9.0", @@ -4429,9 +4429,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -4631,9 +4631,9 @@ } }, "dpop": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.0.tgz", - "integrity": "sha512-r//f6g3uaDdomkz+3M0NdcEAxSGUVMIhF7lxf06aJZjCFhkH//GeZo6JWpz7Uv025s1w/+M5RDNRGxAGNIzm0g==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dpop/-/dpop-1.4.1.tgz", + "integrity": "sha512-+Cus+OlLk9uFWbPZX/RsLpMviYAmyJpJpooto2NDQ0lnk0/S2TblPunC4nVtETOxCIsXvu4YILIOPC7LIHHXIg==" }, "electron-to-chromium": { "version": "1.4.477", @@ -5071,9 +5071,9 @@ "dev": true }, "jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==" + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==" }, "js-tokens": { "version": "4.0.0" From ddc67540b3e2570d5d8cab631c1e67462cedf8b8 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 14:26:08 -0500 Subject: [PATCH 07/15] fixup --- cli/sample.tdf | Bin 1924 -> 0 bytes cli/sample.txt | 1 - 2 files changed, 1 deletion(-) delete mode 100644 cli/sample.tdf delete mode 100644 cli/sample.txt diff --git a/cli/sample.tdf b/cli/sample.tdf deleted file mode 100644 index 9ed727421de4e58d668f24850498519c9e127b8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1924 zcmaJ?&9B=;5KmkBTDTx2Kpf!WQ>DDV*v|Kp3UU(1FCXi~juXeOI5_sc_}h-X`LIhx z5mJvxzy)zZLL7SFh{TCAH~0?_DiZ$%D(jc{>9w@%!81EMGy9wI%+7dl@6|=q`B_V8CnWLkn802$O)TyC=RxFY8GGgGICL$9eL65kZHP;McblM;rWXaLu=TYtj2jc-uvIy6Gzg;SpM$VQIc-HF# z-GR~VQ$q$$Cfgdtazikl*2iSO**r?7ILTOQJ2HRbz8iuqGb`A3=0>n{Yg5`BcZUe}nEGs9FHXgJ ze^}A&vvtczrI?*G&_txCyJ!Z@674vP8SnPn8#DxM(`nGjZZ~WXXsV^u){XT-(sXHi z?;cM4;SWOtK%^NO(r~(A(zmsFD^TZsQk|P*&kihp5?6Jpx07Yj(!53lIAkwnXQzk-I*)+tz^oDqXn%u$CF;%^IZCs_Ex`{{rkYR^rU6^hUG7_;?i79h_eY@ zt>I%+i%q;$(yj+2P*r4*Dw1ANJ=Gj_M3=|>sMDhW*GmCCrBunVRF9=b(z47;fK;Te zJ(Bx2NS(4GiI&=7t0QUV0a2ay@zez>?ih%97!_)dnM+{Ga7a&3Tt zSn&i!Q1i5fi`#DBIo1ZNRg29PO)1904vM5zYGs{c`3NqtpX7zciGl!4tfjA^b%edV z7;J21p)0ns<0Yn|c4j7@y56YMwG?kkDzrfI+>`_~pnyEB0&eE;H18^sQdUyY;ypIu zX(3f5h2|yE1f6N3cuJK|g{`{GV#U1|5=09h_h6S9STcmyTpm9!YEkbPO9vN>)n;7ZWSRkx}%O;+pI)-Jev*EaO*{JMdgv zy9nEml}5E*d%59rS~aqQW7$^@P8hCO%8*?rt2D)CDAfarYWEQ6V32K+PgSq;^a0RIO)}}A4i#srZ5*t-E%EtF1lEwCQ~G?WHHpsyt}}9 zGDS7dg^S|Jp8T^X(Ig+p3<$tD1jxDF3A1ua$qod;kpJC3|^#haB9}oM@(VoF}w=#~n_!bJ+ QFh3sLyg1E)WwcQE7ZXBL9RL6T diff --git a/cli/sample.txt b/cli/sample.txt deleted file mode 100644 index 6b820fd9..00000000 --- a/cli/sample.txt +++ /dev/null @@ -1 +0,0 @@ -hello-world From e26d25eae77e5011dc67b0a9c628ea3ee8750d95 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 14:49:36 -0500 Subject: [PATCH 08/15] fixes for latest service changes --- .../workflows/roundtrip/keycloak_data.yaml | 114 ++++++++++++++++++ .github/workflows/roundtrip/wait-and-test.sh | 2 +- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/roundtrip/keycloak_data.yaml 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/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 From 04e8c5f48af0f116804e5b4d301fa25fa3a8b432 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 14:51:18 -0500 Subject: [PATCH 09/15] Update opentdf.yaml --- .github/workflows/roundtrip/opentdf.yaml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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: From 8a8c5e6a479977afcfff55927d502241c2548ec4 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 15:39:57 -0500 Subject: [PATCH 10/15] Update build.yaml --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 }} From a5a05d43b41e048fff4880ec85298b69d845ae6c Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 16:09:33 -0500 Subject: [PATCH 11/15] fixes --- lib/.prettierignore | 1 + lib/tdf3/src/tdf.ts | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 lib/.prettierignore 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/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index ab5cbb4e..3d7d8bae 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -809,15 +809,13 @@ 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(({ url }) => allowedKases.includes(new URL(url).origin)).map(({ sid }) => sid) - ); + const accessibleSplits = new Set(keyAccess.filter(allowed).map(({ sid }) => sid)); if (splitIds.size > accessibleSplits.size) { - const disallowedKases = new Set( - keyAccess.filter(({ url }) => !allowedKases.includes(url)).map(({ url }) => url) - ); + const disallowedKases = new Set(keyAccess.filter((k) => !allowed(k)).map(({ url }) => url)); throw new KasDecryptError( `Unreconstructable key - disallowed KASes include: ${JSON.stringify([ ...disallowedKases, @@ -834,7 +832,7 @@ export function splitLookupTableFactory( `TODO: Fallback to no split ids. Repetition found for [${kao.url}] on split [${kao.sid}]` ); } - if (allowedKases.includes(kao.url)) { + if (allowed(kao)) { disjunction[kao.url] = kao; } } From c8a743b6cebd4dc873cf98128b6dd87a1a1f2363 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 16:26:11 -0500 Subject: [PATCH 12/15] chore(ci): adds make cli phony target --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From c6b01de0d373788234a6bab9a9274246f857a4c6 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 16:37:34 -0500 Subject: [PATCH 13/15] Update index.ts --- lib/tdf3/src/client/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/tdf3/src/client/index.ts b/lib/tdf3/src/client/index.ts index 8d5becb4..ed65256a 100644 --- a/lib/tdf3/src/client/index.ts +++ b/lib/tdf3/src/client/index.ts @@ -336,12 +336,6 @@ export class Client { publicKey: clientConfig.kasPublicKey, }); } - for (const kasEndpoint of this.allowedKases) { - if (kasEndpoint in this.kasKeys) { - continue; - } - this.kasKeys[kasEndpoint] = fetchKasPublicKey(this.kasEndpoint); - } } /** @@ -394,6 +388,9 @@ export class Client { 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, From c66e61e5d200b8436ab5eac5b00b7acd36f09182 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 16:45:03 -0500 Subject: [PATCH 14/15] sonarcloud suggestions --- lib/tdf3/src/models/encryption-information.ts | 2 +- lib/tdf3/src/tdf.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tdf3/src/models/encryption-information.ts b/lib/tdf3/src/models/encryption-information.ts index a62df9df..0e99efc4 100644 --- a/lib/tdf3/src/models/encryption-information.ts +++ b/lib/tdf3/src/models/encryption-information.ts @@ -77,7 +77,7 @@ export class SplitKey { } async getKeyAccessObjects(policy: Policy, keyInfo: KeyInfo): Promise { - const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort(); + const splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort((a, b) => a.localeCompare(b)); const unwrappedKeySplitBuffers = await keySplit( new Uint8Array(keyInfo.unwrappedKeyBinary.asByteArray()), splitIds.length, diff --git a/lib/tdf3/src/tdf.ts b/lib/tdf3/src/tdf.ts index 3d7d8bae..acdad7d8 100644 --- a/lib/tdf3/src/tdf.ts +++ b/lib/tdf3/src/tdf.ts @@ -811,7 +811,7 @@ export function splitLookupTableFactory( ): 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 splitIds = new Set(keyAccess.map(({ sid }) => sid ?? '')); const accessibleSplits = new Set(keyAccess.filter(allowed).map(({ sid }) => sid)); if (splitIds.size > accessibleSplits.size) { @@ -826,7 +826,7 @@ export function splitLookupTableFactory( [...splitIds].map((s) => [s, {}]) ); for (const kao of keyAccess) { - const disjunction = splitPotentials[kao.sid || '']; + 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}]` @@ -871,8 +871,8 @@ async function unwrapKey({ '' ); } - // TODO: If we have multiple ways of getting a value, try the 'best' way - // or maybe retry across all potential ways + // 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`; From 06a4128d2da98f5d3156404a5fd4c88d8d54f9d0 Mon Sep 17 00:00:00 2001 From: David Mihalcik Date: Wed, 21 Aug 2024 16:45:21 -0500 Subject: [PATCH 15/15] Update encryption-information.ts --- lib/tdf3/src/models/encryption-information.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tdf3/src/models/encryption-information.ts b/lib/tdf3/src/models/encryption-information.ts index 0e99efc4..700ff73c 100644 --- a/lib/tdf3/src/models/encryption-information.ts +++ b/lib/tdf3/src/models/encryption-information.ts @@ -77,7 +77,9 @@ 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 splitIds = [...new Set(this.keyAccess.map(({ sid }) => sid))].sort((a, b) => + a.localeCompare(b) + ); const unwrappedKeySplitBuffers = await keySplit( new Uint8Array(keyInfo.unwrappedKeyBinary.asByteArray()), splitIds.length,