diff --git a/cli/package-lock.json b/cli/package-lock.json index 18eda993..b1a632c4 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-AYYYJYSNJgUXNHJTK8pH+W74udFhwn1Co5/utjdfZZUFBjl2cGn+knfJe/Blrgj0S2VseOUs1nvwPiZYnAS/8w==", + "integrity": "sha512-f+e14B8wFOwThqVCkw4cD56B/Xrhzf+S6GV6Q2qjXUG+pZTgYDe53jAHB2Ro3Xh9FCNtvachJoQS5O6BRsA8dw==", "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.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 74db3084..6bcdc555 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -185,6 +185,7 @@ export const handleArgs = (args: string[]) => { type: 'string', validate: (attributes: string) => attributes.split(','), }) + .boolean('ignoreAllowList') .option('auth', { group: 'Authentication:', type: 'string', @@ -293,6 +294,7 @@ export const handleArgs = (args: string[]) => { async (argv) => { log('DEBUG', 'Running decrypt command'); const allowedKases = argv.allowList?.split(','); + const ignoreAllowList = !!argv.ignoreAllowList; const authProvider = await processAuth(argv); log('DEBUG', `Initialized auth provider ${JSON.stringify(authProvider)}`); @@ -301,6 +303,7 @@ export const handleArgs = (args: string[]) => { log('DEBUG', `TDF3 Client`); const client = new TDF3Client({ allowedKases, + ignoreAllowList, authProvider, kasEndpoint, dpopEnabled: argv.dpop, @@ -318,9 +321,16 @@ export const handleArgs = (args: string[]) => { const dpopEnabled = !!argv.dpop; const client = argv.containerType === 'nano' - ? new NanoTDFClient({ allowedKases, authProvider, kasEndpoint, dpopEnabled }) + ? new NanoTDFClient({ + allowedKases, + ignoreAllowList, + authProvider, + kasEndpoint, + dpopEnabled, + }) : new NanoTDFDatasetClient({ allowedKases, + ignoreAllowList, authProvider, kasEndpoint, dpopEnabled, @@ -376,12 +386,14 @@ export const handleArgs = (args: string[]) => { const authProvider = await processAuth(argv); log('DEBUG', `Initialized auth provider ${JSON.stringify(authProvider)}`); const kasEndpoint = argv.kasEndpoint; + const ignoreAllowList = !!argv.ignoreAllowList; const allowedKases = argv.allowList?.split(','); if ('tdf3' === argv.containerType) { log('DEBUG', `TDF3 Client`); const client = new TDF3Client({ allowedKases, + ignoreAllowList, authProvider, kasEndpoint, dpopEnabled: argv.dpop, diff --git a/lib/src/access.ts b/lib/src/access.ts index 97899e24..1b21a218 100644 --- a/lib/src/access.ts +++ b/lib/src/access.ts @@ -72,11 +72,16 @@ const origin = (u: string): string => { export class OriginAllowList { origins: string[]; - constructor(urls: string[]) { + allowAll: boolean; + constructor(urls: string[], allowAll?: boolean) { this.origins = urls.map(origin); urls.forEach(validateSecureUrl); + this.allowAll = !!allowAll; } allows(url: string): boolean { + if (this.allowAll) { + return true; + } return this.origins.includes(origin(url)); } } diff --git a/lib/src/nanotdf/Client.ts b/lib/src/nanotdf/Client.ts index dd9b85bd..e96f724d 100644 --- a/lib/src/nanotdf/Client.ts +++ b/lib/src/nanotdf/Client.ts @@ -10,6 +10,7 @@ import { cryptoPublicToPem, pemToCryptoPublicKey, validateSecureUrl } from '../u export interface ClientConfig { allowedKases?: string[]; + ignoreAllowList?: boolean; authProvider: AuthProvider; dpopEnabled?: boolean; dpopKeys?: Promise; @@ -145,13 +146,20 @@ export default class Client { } this.iv = 1; } else { - const { allowedKases, authProvider, dpopEnabled, dpopKeys, ephemeralKeyPair, kasEndpoint } = - optsOrOldAuthProvider; + const { + allowedKases, + ignoreAllowList, + authProvider, + dpopEnabled, + dpopKeys, + ephemeralKeyPair, + kasEndpoint, + } = optsOrOldAuthProvider; this.authProvider = authProvider; // TODO Disallow http KAS. For now just log as error validateSecureUrl(kasEndpoint); this.kasUrl = kasEndpoint; - this.allowedKases = new OriginAllowList(allowedKases || [kasEndpoint]); + this.allowedKases = new OriginAllowList(allowedKases || [kasEndpoint], !!ignoreAllowList); this.dpopEnabled = !!dpopEnabled; if (dpopKeys) { this.requestSignerKeyPair = dpopKeys; diff --git a/lib/tdf3/src/client/index.ts b/lib/tdf3/src/client/index.ts index a25b3122..f892b285 100644 --- a/lib/tdf3/src/client/index.ts +++ b/lib/tdf3/src/client/index.ts @@ -137,6 +137,7 @@ export interface ClientConfig { * Defaults to `[kasEndpoint]`. */ allowedKases?: string[]; + ignoreAllowList?: boolean; easEndpoint?: string; // DEPRECATED Ignored keyRewrapEndpoint?: string; @@ -275,7 +276,10 @@ export class Client { const kasOrigin = new URL(this.kasEndpoint).origin; if (clientConfig.allowedKases) { - this.allowedKases = new OriginAllowList(clientConfig.allowedKases); + this.allowedKases = new OriginAllowList( + clientConfig.allowedKases, + !!clientConfig.ignoreAllowList + ); if (!validateSecureUrl(this.kasEndpoint) && !this.allowedKases.allows(kasOrigin)) { throw new TdfError(`Invalid KAS endpoint [${this.kasEndpoint}]`); } @@ -285,7 +289,7 @@ export class Client { `Invalid KAS endpoint [${this.kasEndpoint}]; to force, please list it among allowedKases` ); } - this.allowedKases = new OriginAllowList([kasOrigin]); + this.allowedKases = new OriginAllowList([kasOrigin], !!clientConfig.ignoreAllowList); } this.authProvider = config.authProvider; diff --git a/lib/tests/mocha/unit/tdf.spec.ts b/lib/tests/mocha/unit/tdf.spec.ts index 47d35984..d7cc6414 100644 --- a/lib/tests/mocha/unit/tdf.spec.ts +++ b/lib/tests/mocha/unit/tdf.spec.ts @@ -111,6 +111,21 @@ describe('splitLookupTableFactory', () => { }); }); + it('should return a correct split table for valid input with ignoreAllowList', () => { + const keyAccess: KeyAccessObject[] = [ + { sid: 'split1', type: 'remote', url: 'https://kas1', protocol: 'kas' }, + { sid: 'split2', type: 'remote', url: 'https://kas2', protocol: 'kas' }, + ]; + const allowedKases = new OriginAllowList([], true); + + 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' },