@opentdf/sdk 0.2.0 → 0.3.0-beta.2050
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/access.js +1 -2
- package/dist/cjs/src/auth/auth.js +1 -1
- package/dist/cjs/src/encodings/base64.js +1 -1
- package/dist/cjs/src/index.js +2 -1
- package/dist/cjs/src/nanoclients.js +17 -10
- package/dist/cjs/src/nanotdf/Client.js +1 -1
- package/dist/cjs/src/nanotdf/NanoTDF.js +1 -1
- package/dist/cjs/src/nanotdf/encrypt-dataset.js +1 -1
- package/dist/cjs/src/nanotdf/encrypt.js +1 -1
- package/dist/cjs/src/nanotdf/helpers/getHkdfSalt.js +1 -1
- package/dist/cjs/src/nanotdf-crypto/digest.js +1 -1
- package/dist/cjs/src/nanotdf-crypto/pemPublicToCrypto.js +4 -27
- package/dist/cjs/src/opentdf.js +175 -45
- package/dist/cjs/src/tdf/Policy.js +15 -12
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/tdf3/index.js +1 -1
- package/dist/cjs/tdf3/src/assertions.js +1 -1
- package/dist/cjs/tdf3/src/crypto/salt.js +12 -0
- package/dist/cjs/tdf3/src/models/attribute.js +3 -0
- package/dist/cjs/tdf3/src/models/index.js +2 -2
- package/dist/cjs/tdf3/src/models/key-access.js +3 -2
- package/dist/cjs/tdf3/src/tdf.js +8 -3
- package/dist/types/src/access.d.ts.map +1 -1
- package/dist/types/src/auth/auth.d.ts +2 -2
- package/dist/types/src/auth/auth.d.ts.map +1 -1
- package/dist/types/src/auth/providers.d.ts.map +1 -1
- package/dist/types/src/encodings/base64.d.ts +1 -1
- package/dist/types/src/encodings/base64.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/nanoclients.d.ts +10 -11
- package/dist/types/src/nanoclients.d.ts.map +1 -1
- package/dist/types/src/nanotdf/Client.d.ts +1 -2
- package/dist/types/src/nanotdf/Client.d.ts.map +1 -1
- package/dist/types/src/nanotdf/NanoTDF.d.ts +1 -2
- package/dist/types/src/nanotdf/NanoTDF.d.ts.map +1 -1
- package/dist/types/src/nanotdf/encrypt-dataset.d.ts +1 -2
- package/dist/types/src/nanotdf/encrypt-dataset.d.ts.map +1 -1
- package/dist/types/src/nanotdf/encrypt.d.ts +1 -2
- package/dist/types/src/nanotdf/encrypt.d.ts.map +1 -1
- package/dist/types/src/nanotdf/helpers/getHkdfSalt.d.ts +1 -2
- package/dist/types/src/nanotdf/helpers/getHkdfSalt.d.ts.map +1 -1
- package/dist/types/src/nanotdf-crypto/digest.d.ts +1 -2
- package/dist/types/src/nanotdf-crypto/digest.d.ts.map +1 -1
- package/dist/types/src/nanotdf-crypto/pemPublicToCrypto.d.ts.map +1 -1
- package/dist/types/src/opentdf.d.ts +29 -5
- package/dist/types/src/opentdf.d.ts.map +1 -1
- package/dist/types/src/seekable.d.ts.map +1 -1
- package/dist/types/src/tdf/Policy.d.ts +4 -2
- package/dist/types/src/tdf/Policy.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/tdf3/index.d.ts +1 -1
- package/dist/types/tdf3/index.d.ts.map +1 -1
- package/dist/types/tdf3/src/assertions.d.ts +1 -2
- package/dist/types/tdf3/src/assertions.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/DecoratedReadableStream.d.ts +1 -1
- package/dist/types/tdf3/src/client/index.d.ts +1 -6
- package/dist/types/tdf3/src/client/index.d.ts.map +1 -1
- package/dist/types/tdf3/src/client/validation.d.ts.map +1 -1
- package/dist/types/tdf3/src/crypto/crypto-utils.d.ts.map +1 -1
- package/dist/types/tdf3/src/crypto/salt.d.ts +2 -0
- package/dist/types/tdf3/src/crypto/salt.d.ts.map +1 -0
- package/dist/types/tdf3/src/models/attribute.d.ts +16 -0
- package/dist/types/tdf3/src/models/attribute.d.ts.map +1 -0
- package/dist/types/tdf3/src/models/index.d.ts +1 -1
- package/dist/types/tdf3/src/models/index.d.ts.map +1 -1
- package/dist/types/tdf3/src/models/key-access.d.ts.map +1 -1
- package/dist/types/tdf3/src/models/payload.d.ts +1 -0
- package/dist/types/tdf3/src/models/payload.d.ts.map +1 -1
- package/dist/types/tdf3/src/models/policy.d.ts +1 -1
- package/dist/types/tdf3/src/models/policy.d.ts.map +1 -1
- package/dist/types/tdf3/src/tdf.d.ts +5 -9
- package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
- package/dist/web/src/access.js +1 -2
- package/dist/web/src/auth/auth.js +1 -1
- package/dist/web/src/encodings/base64.js +1 -1
- package/dist/web/src/index.js +2 -1
- package/dist/web/src/nanoclients.js +18 -11
- package/dist/web/src/nanotdf/Client.js +1 -1
- package/dist/web/src/nanotdf/NanoTDF.js +1 -1
- package/dist/web/src/nanotdf/encrypt-dataset.js +1 -1
- package/dist/web/src/nanotdf/encrypt.js +1 -1
- package/dist/web/src/nanotdf/helpers/getHkdfSalt.js +1 -1
- package/dist/web/src/nanotdf-crypto/digest.js +1 -1
- package/dist/web/src/nanotdf-crypto/pemPublicToCrypto.js +4 -27
- package/dist/web/src/opentdf.js +174 -44
- package/dist/web/src/tdf/Policy.js +13 -10
- package/dist/web/src/version.js +1 -1
- package/dist/web/tdf3/index.js +1 -1
- package/dist/web/tdf3/src/assertions.js +1 -1
- package/dist/web/tdf3/src/crypto/salt.js +9 -0
- package/dist/web/tdf3/src/models/attribute.js +2 -0
- package/dist/web/tdf3/src/models/index.js +2 -2
- package/dist/web/tdf3/src/models/key-access.js +3 -2
- package/dist/web/tdf3/src/tdf.js +7 -3
- package/package.json +12 -12
- package/src/access.ts +0 -1
- package/src/auth/auth.ts +2 -2
- package/src/encodings/base64.ts +1 -1
- package/src/index.ts +1 -0
- package/src/nanoclients.ts +24 -23
- package/src/nanotdf/Client.ts +2 -3
- package/src/nanotdf/NanoTDF.ts +1 -2
- package/src/nanotdf/encrypt-dataset.ts +1 -2
- package/src/nanotdf/encrypt.ts +1 -2
- package/src/nanotdf/helpers/getHkdfSalt.ts +1 -3
- package/src/nanotdf-crypto/digest.ts +1 -3
- package/src/nanotdf-crypto/pemPublicToCrypto.ts +3 -33
- package/src/opentdf.ts +249 -54
- package/src/tdf/Policy.ts +15 -9
- package/src/version.ts +1 -1
- package/tdf3/index.ts +2 -1
- package/tdf3/src/assertions.ts +2 -2
- package/tdf3/src/crypto/salt.ts +11 -0
- package/tdf3/src/models/attribute.ts +26 -0
- package/tdf3/src/models/index.ts +1 -1
- package/tdf3/src/models/key-access.ts +2 -1
- package/tdf3/src/models/payload.ts +1 -0
- package/tdf3/src/models/policy.ts +1 -1
- package/tdf3/src/tdf.ts +22 -13
- package/dist/cjs/src/tdf/PolicyObject.js +0 -3
- package/dist/cjs/src/tdf/TypedArray.js +0 -3
- package/dist/cjs/tdf3/src/models/attribute-set.js +0 -122
- package/dist/types/src/tdf/PolicyObject.d.ts +0 -10
- package/dist/types/src/tdf/PolicyObject.d.ts.map +0 -1
- package/dist/types/src/tdf/TypedArray.d.ts +0 -2
- package/dist/types/src/tdf/TypedArray.d.ts.map +0 -1
- package/dist/types/tdf3/src/models/attribute-set.d.ts +0 -65
- package/dist/types/tdf3/src/models/attribute-set.d.ts.map +0 -1
- package/dist/web/src/tdf/PolicyObject.js +0 -2
- package/dist/web/src/tdf/TypedArray.js +0 -2
- package/dist/web/tdf3/src/models/attribute-set.js +0 -118
- package/src/tdf/PolicyObject.ts +0 -11
- package/src/tdf/TypedArray.ts +0 -10
- package/tdf3/src/models/attribute-set.ts +0 -142
package/src/opentdf.ts
CHANGED
|
@@ -6,7 +6,7 @@ import NanoTDF from './nanotdf/NanoTDF.js';
|
|
|
6
6
|
import decryptNanoTDF from './nanotdf/decrypt.js';
|
|
7
7
|
import Client from './nanotdf/Client.js';
|
|
8
8
|
import Header from './nanotdf/models/Header.js';
|
|
9
|
-
import { fromSource, sourceToStream, type Source } from './seekable.js';
|
|
9
|
+
import { Chunker, fromSource, sourceToStream, type Source } from './seekable.js';
|
|
10
10
|
import { Client as TDF3Client } from '../tdf3/src/client/index.js';
|
|
11
11
|
import {
|
|
12
12
|
type Assertion,
|
|
@@ -22,7 +22,15 @@ import {
|
|
|
22
22
|
type EncryptionInformation,
|
|
23
23
|
} from '../tdf3/src/models/encryption-information.js';
|
|
24
24
|
import { type KeyAccessObject } from '../tdf3/src/models/key-access.js';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
decryptStreamFrom,
|
|
27
|
+
InspectedTDFOverview,
|
|
28
|
+
loadTDFStream,
|
|
29
|
+
type IntegrityAlgorithm,
|
|
30
|
+
} from '../tdf3/src/tdf.js';
|
|
31
|
+
import { base64 } from './encodings/index.js';
|
|
32
|
+
import PolicyType from './nanotdf/enum/PolicyTypeEnum.js';
|
|
33
|
+
import { Policy } from '../tdf3/src/models/policy.js';
|
|
26
34
|
|
|
27
35
|
export {
|
|
28
36
|
type Assertion,
|
|
@@ -248,6 +256,30 @@ export class RewrapCache {
|
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
|
|
259
|
+
/**
|
|
260
|
+
* A TDF reader that can decrypt and inspect a TDF file.
|
|
261
|
+
*/
|
|
262
|
+
export type TDFReader = {
|
|
263
|
+
/**
|
|
264
|
+
* Decrypt the payload.
|
|
265
|
+
*/
|
|
266
|
+
decrypt: () => Promise<DecoratedStream>;
|
|
267
|
+
/**
|
|
268
|
+
* Mark this reader as closed and release any resources, such as open files.
|
|
269
|
+
*/
|
|
270
|
+
close: () => Promise<void>;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Only present on ZTDF files
|
|
274
|
+
*/
|
|
275
|
+
manifest: () => Promise<Manifest>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* @returns Any data attributes found in the policy. Currently only works for plain text, embedded policies (not remote or encrypted policies)
|
|
279
|
+
*/
|
|
280
|
+
attributes: () => Promise<string[]>;
|
|
281
|
+
};
|
|
282
|
+
|
|
251
283
|
// SDK for dealing with OpenTDF data and policy services.
|
|
252
284
|
export class OpenTDF {
|
|
253
285
|
// Configuration service and more is at this URL/connectRPC endpoint
|
|
@@ -260,7 +292,7 @@ export class OpenTDF {
|
|
|
260
292
|
|
|
261
293
|
// Header cache for reading nanotdf collections
|
|
262
294
|
private readonly rewrapCache: RewrapCache;
|
|
263
|
-
|
|
295
|
+
readonly tdf3Client: TDF3Client;
|
|
264
296
|
|
|
265
297
|
constructor({
|
|
266
298
|
authProvider,
|
|
@@ -311,7 +343,9 @@ export class OpenTDF {
|
|
|
311
343
|
* Creates a new collection object, which can be used to encrypt a series of data with the same policy.
|
|
312
344
|
* @returns
|
|
313
345
|
*/
|
|
314
|
-
async createNanoTDFCollection(
|
|
346
|
+
async createNanoTDFCollection(
|
|
347
|
+
opts: CreateNanoTDFCollectionOptions
|
|
348
|
+
): Promise<NanoTDFCollectionWriter> {
|
|
315
349
|
opts = { ...this.defaultCreateOptions, ...opts };
|
|
316
350
|
return new Collection(this.authProvider, opts);
|
|
317
351
|
}
|
|
@@ -340,66 +374,227 @@ export class OpenTDF {
|
|
|
340
374
|
}
|
|
341
375
|
|
|
342
376
|
/**
|
|
343
|
-
*
|
|
344
|
-
* @param
|
|
377
|
+
* Opens a TDF file for inspection and decryption.
|
|
378
|
+
* @param opts the file to open, and any appropriate configuration options
|
|
379
|
+
* @returns
|
|
345
380
|
*/
|
|
346
|
-
|
|
381
|
+
open(opts: ReadOptions): TDFReader {
|
|
347
382
|
opts = { ...this.defaultReadOptions, ...opts };
|
|
348
|
-
|
|
383
|
+
return new UnknownTypeReader(this, opts, this.rewrapCache);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async read(opts: ReadOptions): Promise<DecoratedStream> {
|
|
387
|
+
const reader = this.open(opts);
|
|
388
|
+
return reader.decrypt();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
close() {
|
|
392
|
+
this.rewrapCache.close();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
class UnknownTypeReader {
|
|
397
|
+
delegate: Promise<TDFReader>;
|
|
398
|
+
state: 'init' | 'resolving' | 'loaded' | 'decrypting' | 'closing' | 'done' | 'error' = 'init';
|
|
399
|
+
constructor(
|
|
400
|
+
readonly outer: OpenTDF,
|
|
401
|
+
readonly opts: ReadOptions,
|
|
402
|
+
private readonly rewrapCache: RewrapCache
|
|
403
|
+
) {
|
|
404
|
+
this.delegate = this.resolveType();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async resolveType(): Promise<TDFReader> {
|
|
408
|
+
if (this.state === 'done') {
|
|
409
|
+
throw new ConfigurationError('reader is closed');
|
|
410
|
+
}
|
|
411
|
+
this.state = 'resolving';
|
|
412
|
+
const chunker = await fromSource(this.opts.source);
|
|
349
413
|
const prefix = await chunker(0, 3);
|
|
350
|
-
// switch for prefix, if starts with `PK` in ascii, or `L1L` in ascii:
|
|
351
414
|
if (prefix[0] === 0x50 && prefix[1] === 0x4b) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
source: opts.source,
|
|
355
|
-
allowList,
|
|
356
|
-
assertionVerificationKeys: opts.assertionVerificationKeys,
|
|
357
|
-
noVerifyAssertions: opts.noVerify,
|
|
358
|
-
wrappingKeyAlgorithm: opts.wrappingKeyAlgorithm,
|
|
359
|
-
});
|
|
360
|
-
const stream: DecoratedStream = oldStream.stream;
|
|
361
|
-
stream.metadata = Promise.resolve(oldStream.metadata);
|
|
362
|
-
return stream;
|
|
415
|
+
this.state = 'loaded';
|
|
416
|
+
return new ZTDFReader(this.outer.tdf3Client, this.opts, chunker);
|
|
363
417
|
} else if (prefix[0] === 0x4c && prefix[1] === 0x31 && prefix[2] === 0x4c) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
418
|
+
this.state = 'loaded';
|
|
419
|
+
return new NanoTDFReader(this.outer, this.opts, chunker, this.rewrapCache);
|
|
420
|
+
}
|
|
421
|
+
this.state = 'done';
|
|
422
|
+
throw new InvalidFileError(`unsupported format; prefix not recognized ${prefix}`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async decrypt(): Promise<DecoratedStream> {
|
|
426
|
+
const actual = await this.delegate;
|
|
427
|
+
return actual.decrypt();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async attributes(): Promise<string[]> {
|
|
431
|
+
const actual = await this.delegate;
|
|
432
|
+
return actual.attributes();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async manifest(): Promise<Manifest> {
|
|
436
|
+
const actual = await this.delegate;
|
|
437
|
+
return actual.manifest();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async close() {
|
|
441
|
+
if (this.state === 'done') {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (this.state === 'init') {
|
|
445
|
+
// delegate resolve never started
|
|
446
|
+
this.state = 'done';
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.state = 'closing';
|
|
450
|
+
const actual = await this.delegate;
|
|
451
|
+
return actual.close().then(() => {
|
|
452
|
+
this.state = 'done';
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
class NanoTDFReader {
|
|
458
|
+
container: Promise<NanoTDF>;
|
|
459
|
+
constructor(
|
|
460
|
+
readonly outer: OpenTDF,
|
|
461
|
+
readonly opts: ReadOptions,
|
|
462
|
+
readonly chunker: Chunker,
|
|
463
|
+
private readonly rewrapCache: RewrapCache
|
|
464
|
+
) {
|
|
465
|
+
// lazily load the container
|
|
466
|
+
this.container = new Promise(async (resolve, reject) => {
|
|
467
|
+
try {
|
|
468
|
+
const ciphertext = await chunker();
|
|
469
|
+
const nanotdf = NanoTDF.from(ciphertext);
|
|
470
|
+
resolve(nanotdf);
|
|
471
|
+
} catch (e) {
|
|
472
|
+
reject(e);
|
|
392
473
|
}
|
|
393
|
-
|
|
394
|
-
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
async decrypt(): Promise<DecoratedStream> {
|
|
478
|
+
const nanotdf = await this.container;
|
|
479
|
+
const cachedDEK = this.rewrapCache.get(nanotdf.header.ephemeralPublicKey);
|
|
480
|
+
if (cachedDEK) {
|
|
481
|
+
const r: DecoratedStream = await streamify(decryptNanoTDF(cachedDEK, nanotdf));
|
|
395
482
|
r.header = nanotdf.header;
|
|
396
483
|
return r;
|
|
397
484
|
}
|
|
398
|
-
|
|
485
|
+
const nc = new Client({
|
|
486
|
+
allowedKases: this.opts.allowedKASEndpoints,
|
|
487
|
+
authProvider: this.outer.authProvider,
|
|
488
|
+
ignoreAllowList: this.opts.ignoreAllowlist,
|
|
489
|
+
dpopEnabled: this.outer.dpopEnabled,
|
|
490
|
+
dpopKeys: this.outer.dpopKeys,
|
|
491
|
+
kasEndpoint: this.opts.allowedKASEndpoints?.[0] || 'https://disallow.all.invalid',
|
|
492
|
+
});
|
|
493
|
+
// TODO: The version number should be fetched from the API
|
|
494
|
+
const version = '0.0.1';
|
|
495
|
+
// Rewrap key on every request
|
|
496
|
+
const dek = await nc.rewrapKey(
|
|
497
|
+
nanotdf.header.toBuffer(),
|
|
498
|
+
nanotdf.header.getKasRewrapUrl(),
|
|
499
|
+
nanotdf.header.magicNumberVersion,
|
|
500
|
+
version
|
|
501
|
+
);
|
|
502
|
+
if (!dek) {
|
|
503
|
+
// These should have thrown already.
|
|
504
|
+
throw new Error('internal: key rewrap failure');
|
|
505
|
+
}
|
|
506
|
+
this.rewrapCache.set(nanotdf.header.ephemeralPublicKey, dek);
|
|
507
|
+
const r: DecoratedStream = await streamify(decryptNanoTDF(dek, nanotdf));
|
|
508
|
+
// TODO figure out how to attach policy and metadata to the stream
|
|
509
|
+
r.header = nanotdf.header;
|
|
510
|
+
return r;
|
|
399
511
|
}
|
|
400
512
|
|
|
401
|
-
close() {
|
|
402
|
-
|
|
513
|
+
async close() {}
|
|
514
|
+
|
|
515
|
+
async manifest(): Promise<Manifest> {
|
|
516
|
+
return {} as Manifest;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async attributes(): Promise<string[]> {
|
|
520
|
+
const nanotdf = await this.container;
|
|
521
|
+
if (!nanotdf.header.policy?.content) {
|
|
522
|
+
return [];
|
|
523
|
+
}
|
|
524
|
+
if (nanotdf.header.policy.type !== PolicyType.EmbeddedText) {
|
|
525
|
+
throw new Error('unsupported policy type');
|
|
526
|
+
}
|
|
527
|
+
const policyString = new TextDecoder().decode(nanotdf.header.policy.content);
|
|
528
|
+
const policy = JSON.parse(policyString) as Policy;
|
|
529
|
+
return policy?.body?.dataAttributes.map((a) => a.attribute) || [];
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class ZTDFReader {
|
|
534
|
+
overview: Promise<InspectedTDFOverview>;
|
|
535
|
+
constructor(
|
|
536
|
+
readonly client: TDF3Client,
|
|
537
|
+
readonly opts: ReadOptions,
|
|
538
|
+
readonly source: Chunker
|
|
539
|
+
) {
|
|
540
|
+
this.overview = loadTDFStream(source);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async decrypt(): Promise<DecoratedStream> {
|
|
544
|
+
const {
|
|
545
|
+
assertionVerificationKeys,
|
|
546
|
+
noVerify: noVerifyAssertions,
|
|
547
|
+
wrappingKeyAlgorithm,
|
|
548
|
+
} = this.opts;
|
|
549
|
+
const allowList = new OriginAllowList(
|
|
550
|
+
this.opts.allowedKASEndpoints ?? [],
|
|
551
|
+
this.opts.ignoreAllowlist
|
|
552
|
+
);
|
|
553
|
+
const dpopKeys = await this.client.dpopKeys;
|
|
554
|
+
|
|
555
|
+
const { authProvider, cryptoService } = this.client;
|
|
556
|
+
if (!authProvider) {
|
|
557
|
+
throw new ConfigurationError('authProvider is required');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const overview = await this.overview;
|
|
561
|
+
const oldStream = await decryptStreamFrom(
|
|
562
|
+
{
|
|
563
|
+
allowList,
|
|
564
|
+
authProvider,
|
|
565
|
+
chunker: this.source,
|
|
566
|
+
concurrencyLimit: 1,
|
|
567
|
+
cryptoService,
|
|
568
|
+
dpopKeys,
|
|
569
|
+
fileStreamServiceWorker: this.client.clientConfig.fileStreamServiceWorker,
|
|
570
|
+
keyMiddleware: async (k) => k,
|
|
571
|
+
progressHandler: this.client.clientConfig.progressHandler,
|
|
572
|
+
assertionVerificationKeys,
|
|
573
|
+
noVerifyAssertions,
|
|
574
|
+
wrappingKeyAlgorithm,
|
|
575
|
+
},
|
|
576
|
+
overview
|
|
577
|
+
);
|
|
578
|
+
const stream: DecoratedStream = oldStream.stream;
|
|
579
|
+
stream.manifest = Promise.resolve(overview.manifest);
|
|
580
|
+
stream.metadata = Promise.resolve(oldStream.metadata);
|
|
581
|
+
return stream;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async close() {
|
|
585
|
+
// TODO figure out how to close a chunker, if we want to.
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async manifest(): Promise<Manifest> {
|
|
589
|
+
const overview = await this.overview;
|
|
590
|
+
return overview.manifest;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async attributes(): Promise<string[]> {
|
|
594
|
+
const manifest = await this.manifest();
|
|
595
|
+
const policyJSON = base64.decode(manifest.encryptionInformation.policy);
|
|
596
|
+
const policy = JSON.parse(policyJSON) as Policy;
|
|
597
|
+
return policy?.body?.dataAttributes.map((a) => a.attribute) || [];
|
|
403
598
|
}
|
|
404
599
|
}
|
|
405
600
|
|
|
@@ -415,7 +610,7 @@ async function streamify(ab: Promise<ArrayBuffer>): Promise<ReadableStream<Uint8
|
|
|
415
610
|
return stream;
|
|
416
611
|
}
|
|
417
612
|
|
|
418
|
-
export type
|
|
613
|
+
export type NanoTDFCollectionWriter = {
|
|
419
614
|
encrypt: (source: Source) => Promise<ReadableStream<Uint8Array>>;
|
|
420
615
|
close: () => Promise<void>;
|
|
421
616
|
};
|
package/src/tdf/Policy.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { type AttributeObject } from './AttributeObject.js';
|
|
2
1
|
import { v4 as uuid } from 'uuid';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { type AttributeObject } from '../../tdf3/src/models/attribute.js';
|
|
4
|
+
import { type Policy } from '../../tdf3/src/models/policy.js';
|
|
5
|
+
|
|
6
|
+
export class PolicyBuilder {
|
|
5
7
|
static CURRENT_VERSION = '1.1.0';
|
|
6
8
|
|
|
7
9
|
private uuidStr = uuid();
|
|
@@ -33,18 +35,22 @@ export class Policy {
|
|
|
33
35
|
this.dataAttributesList.push(attribute);
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
toPolicy(): Policy {
|
|
39
|
+
return {
|
|
40
|
+
uuid: this.uuidStr,
|
|
41
|
+
body: {
|
|
42
|
+
dataAttributes: this.dataAttributesList,
|
|
43
|
+
dissem: this.dissemList,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
/**
|
|
37
49
|
* Returns the JSON string of Policy object
|
|
38
50
|
*
|
|
39
51
|
* @return {string} [The constructed Policy object as JSON string]
|
|
40
52
|
*/
|
|
41
53
|
toJSON(): string {
|
|
42
|
-
return JSON.stringify(
|
|
43
|
-
uuid: this.uuidStr,
|
|
44
|
-
body: {
|
|
45
|
-
dataAttributes: this.dataAttributesList,
|
|
46
|
-
dissem: this.dissemList,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
54
|
+
return JSON.stringify(this.toPolicy());
|
|
49
55
|
}
|
|
50
56
|
}
|
package/src/version.ts
CHANGED
package/tdf3/index.ts
CHANGED
package/tdf3/src/assertions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { canonicalizeEx } from 'json-canonicalize';
|
|
2
|
-
import {
|
|
2
|
+
import { SignJWT, jwtVerify } from 'jose';
|
|
3
3
|
import { base64, hex } from '../../src/encodings/index.js';
|
|
4
4
|
import { ConfigurationError, IntegrityError, InvalidFileError } from '../../src/errors.js';
|
|
5
5
|
|
|
@@ -185,7 +185,7 @@ export async function CreateAssertion(
|
|
|
185
185
|
|
|
186
186
|
export type AssertionKey = {
|
|
187
187
|
alg: AssertionKeyAlg;
|
|
188
|
-
key:
|
|
188
|
+
key: CryptoKey | Uint8Array;
|
|
189
189
|
};
|
|
190
190
|
|
|
191
191
|
// AssertionConfig is a shadow of Assertion with the addition of the signing key.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const generateSalt = async () => {
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
const data = encoder.encode('TDF');
|
|
4
|
+
|
|
5
|
+
// Generate hash
|
|
6
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
7
|
+
|
|
8
|
+
return new Uint8Array(hashBuffer);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const ztdfSalt = generateSalt();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about a data or entity attribute, its meaning and interpretation.
|
|
3
|
+
* While usually we just refer to an attribute by its URL,
|
|
4
|
+
* we often need to store additional information about it,
|
|
5
|
+
* for display or analysis.
|
|
6
|
+
*/
|
|
7
|
+
export type AttributeObject = {
|
|
8
|
+
// The fully qualified name of the attribute, generally a URL
|
|
9
|
+
attribute: string;
|
|
10
|
+
// Optional descriptive name of the attribute
|
|
11
|
+
displayName?: string;
|
|
12
|
+
// Indicates a default attribute, usually for all policies associated with a KAS
|
|
13
|
+
isDefault?: boolean;
|
|
14
|
+
|
|
15
|
+
// Optional: A cryptographically bound version of the attribute. Deprecated: use a JWS with this as the payload.
|
|
16
|
+
jwt?: string;
|
|
17
|
+
|
|
18
|
+
// A KAS that is associated with the attribute.
|
|
19
|
+
kasUrl?: string;
|
|
20
|
+
|
|
21
|
+
// The preferred public key for the attribute
|
|
22
|
+
kid?: string;
|
|
23
|
+
|
|
24
|
+
// The public key value for the attribute
|
|
25
|
+
pubKey?: string;
|
|
26
|
+
};
|
package/tdf3/src/models/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { pemPublicToCrypto } from '../../../src/nanotdf-crypto/pemPublicToCrypto
|
|
|
5
5
|
import { cryptoPublicToPem } from '../../../src/utils.js';
|
|
6
6
|
import { Binary } from '../binary.js';
|
|
7
7
|
import * as cryptoService from '../crypto/index.js';
|
|
8
|
+
import { ztdfSalt } from '../crypto/salt.js';
|
|
8
9
|
import { Policy } from './policy.js';
|
|
9
10
|
|
|
10
11
|
export type KeyAccessType = 'remote' | 'wrapped' | 'ec-wrapped';
|
|
@@ -44,7 +45,7 @@ export class ECWrapped {
|
|
|
44
45
|
pemPublicToCrypto(this.publicKey),
|
|
45
46
|
]);
|
|
46
47
|
const kek = await keyAgreement(ek.privateKey, clientPublicKey, {
|
|
47
|
-
hkdfSalt:
|
|
48
|
+
hkdfSalt: await ztdfSalt,
|
|
48
49
|
hkdfHash: 'SHA-256',
|
|
49
50
|
});
|
|
50
51
|
const iv = generateRandomNumber(12);
|
package/tdf3/src/tdf.ts
CHANGED
|
@@ -24,7 +24,6 @@ import { generateKeyPair } from '../../src/nanotdf-crypto/generateKeyPair.js';
|
|
|
24
24
|
import { keyAgreement } from '../../src/nanotdf-crypto/keyAgreement.js';
|
|
25
25
|
import { pemPublicToCrypto } from '../../src/nanotdf-crypto/pemPublicToCrypto.js';
|
|
26
26
|
import { type Chunker } from '../../src/seekable.js';
|
|
27
|
-
import { PolicyObject } from '../../src/tdf/PolicyObject.js';
|
|
28
27
|
import { tdfSpecVersion } from '../../src/version.js';
|
|
29
28
|
import { AssertionConfig, AssertionKey, AssertionVerificationKeys } from './assertions.js';
|
|
30
29
|
import * as assertions from './assertions.js';
|
|
@@ -54,6 +53,8 @@ import {
|
|
|
54
53
|
import { unsigned } from './utils/buffer-crc32.js';
|
|
55
54
|
import { ZipReader, ZipWriter, keyMerge, concatUint8 } from './utils/index.js';
|
|
56
55
|
import { CentralDirectory } from './utils/zip-reader.js';
|
|
56
|
+
import { ztdfSalt } from './crypto/salt.js';
|
|
57
|
+
import { Payload } from './models/payload.js';
|
|
57
58
|
|
|
58
59
|
// TODO: input validation on manifest JSON
|
|
59
60
|
const DEFAULT_SEGMENT_SIZE = 1024 * 1024;
|
|
@@ -72,12 +73,7 @@ export type EncryptionOptions = {
|
|
|
72
73
|
|
|
73
74
|
type KeyMiddleware = DecryptParams['keyMiddleware'];
|
|
74
75
|
|
|
75
|
-
export type Metadata =
|
|
76
|
-
connectOptions?: {
|
|
77
|
-
testUrl: string;
|
|
78
|
-
};
|
|
79
|
-
policyObject?: PolicyObject;
|
|
80
|
-
};
|
|
76
|
+
export type Metadata = unknown;
|
|
81
77
|
|
|
82
78
|
export type BuildKeyAccess = {
|
|
83
79
|
type: KeyAccessType;
|
|
@@ -291,7 +287,7 @@ async function _generateManifest(
|
|
|
291
287
|
mimeType: string | undefined
|
|
292
288
|
): Promise<Manifest> {
|
|
293
289
|
// (maybe) Fields are quoted to avoid renaming
|
|
294
|
-
const payload = {
|
|
290
|
+
const payload: Payload = {
|
|
295
291
|
type: 'reference',
|
|
296
292
|
url: '0.payload',
|
|
297
293
|
protocol: 'zip',
|
|
@@ -596,10 +592,14 @@ export async function writeStream(cfg: EncryptConfiguration): Promise<DecoratedR
|
|
|
596
592
|
}
|
|
597
593
|
}
|
|
598
594
|
|
|
595
|
+
export type InspectedTDFOverview = {
|
|
596
|
+
manifest: Manifest;
|
|
597
|
+
zipReader: ZipReader;
|
|
598
|
+
centralDirectory: CentralDirectory[];
|
|
599
|
+
};
|
|
600
|
+
|
|
599
601
|
// load the TDF as a stream in memory, for further use in reading and key syncing
|
|
600
|
-
export async function loadTDFStream(
|
|
601
|
-
chunker: Chunker
|
|
602
|
-
): Promise<{ manifest: Manifest; zipReader: ZipReader; centralDirectory: CentralDirectory[] }> {
|
|
602
|
+
export async function loadTDFStream(chunker: Chunker): Promise<InspectedTDFOverview> {
|
|
603
603
|
const zipReader = new ZipReader(chunker);
|
|
604
604
|
const centralDirectory = await zipReader.getCentralDirectory();
|
|
605
605
|
const manifest = await zipReader.getManifest(centralDirectory, '0.manifest.json');
|
|
@@ -707,7 +707,7 @@ async function unwrapKey({
|
|
|
707
707
|
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
|
|
708
708
|
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
|
|
709
709
|
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
|
|
710
|
-
hkdfSalt:
|
|
710
|
+
hkdfSalt: await ztdfSalt,
|
|
711
711
|
hkdfHash: 'SHA-256',
|
|
712
712
|
});
|
|
713
713
|
const wrappedKeyAndNonce = base64.decodeArrayBuffer(entityWrappedKey);
|
|
@@ -919,6 +919,14 @@ export async function sliceAndDecrypt({
|
|
|
919
919
|
}
|
|
920
920
|
|
|
921
921
|
export async function readStream(cfg: DecryptConfiguration) {
|
|
922
|
+
const overview = await loadTDFStream(cfg.chunker);
|
|
923
|
+
return decryptStreamFrom(cfg, overview);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
export async function decryptStreamFrom(
|
|
927
|
+
cfg: DecryptConfiguration,
|
|
928
|
+
{ manifest, zipReader, centralDirectory }: InspectedTDFOverview
|
|
929
|
+
) {
|
|
922
930
|
let { allowList } = cfg;
|
|
923
931
|
if (!allowList) {
|
|
924
932
|
if (!cfg.allowedKases) {
|
|
@@ -926,10 +934,11 @@ export async function readStream(cfg: DecryptConfiguration) {
|
|
|
926
934
|
}
|
|
927
935
|
allowList = new OriginAllowList(cfg.allowedKases);
|
|
928
936
|
}
|
|
929
|
-
|
|
937
|
+
|
|
930
938
|
if (!manifest) {
|
|
931
939
|
throw new InvalidFileError('Missing manifest data');
|
|
932
940
|
}
|
|
941
|
+
|
|
933
942
|
cfg.keyMiddleware ??= async (key) => key;
|
|
934
943
|
|
|
935
944
|
const {
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUG9saWN5T2JqZWN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3RkZi9Qb2xpY3lPYmplY3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHlwZWRBcnJheS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy90ZGYvVHlwZWRBcnJheS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|