@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.
Files changed (136) hide show
  1. package/dist/cjs/src/access.js +1 -2
  2. package/dist/cjs/src/auth/auth.js +1 -1
  3. package/dist/cjs/src/encodings/base64.js +1 -1
  4. package/dist/cjs/src/index.js +2 -1
  5. package/dist/cjs/src/nanoclients.js +17 -10
  6. package/dist/cjs/src/nanotdf/Client.js +1 -1
  7. package/dist/cjs/src/nanotdf/NanoTDF.js +1 -1
  8. package/dist/cjs/src/nanotdf/encrypt-dataset.js +1 -1
  9. package/dist/cjs/src/nanotdf/encrypt.js +1 -1
  10. package/dist/cjs/src/nanotdf/helpers/getHkdfSalt.js +1 -1
  11. package/dist/cjs/src/nanotdf-crypto/digest.js +1 -1
  12. package/dist/cjs/src/nanotdf-crypto/pemPublicToCrypto.js +4 -27
  13. package/dist/cjs/src/opentdf.js +175 -45
  14. package/dist/cjs/src/tdf/Policy.js +15 -12
  15. package/dist/cjs/src/version.js +1 -1
  16. package/dist/cjs/tdf3/index.js +1 -1
  17. package/dist/cjs/tdf3/src/assertions.js +1 -1
  18. package/dist/cjs/tdf3/src/crypto/salt.js +12 -0
  19. package/dist/cjs/tdf3/src/models/attribute.js +3 -0
  20. package/dist/cjs/tdf3/src/models/index.js +2 -2
  21. package/dist/cjs/tdf3/src/models/key-access.js +3 -2
  22. package/dist/cjs/tdf3/src/tdf.js +8 -3
  23. package/dist/types/src/access.d.ts.map +1 -1
  24. package/dist/types/src/auth/auth.d.ts +2 -2
  25. package/dist/types/src/auth/auth.d.ts.map +1 -1
  26. package/dist/types/src/auth/providers.d.ts.map +1 -1
  27. package/dist/types/src/encodings/base64.d.ts +1 -1
  28. package/dist/types/src/encodings/base64.d.ts.map +1 -1
  29. package/dist/types/src/index.d.ts +1 -0
  30. package/dist/types/src/index.d.ts.map +1 -1
  31. package/dist/types/src/nanoclients.d.ts +10 -11
  32. package/dist/types/src/nanoclients.d.ts.map +1 -1
  33. package/dist/types/src/nanotdf/Client.d.ts +1 -2
  34. package/dist/types/src/nanotdf/Client.d.ts.map +1 -1
  35. package/dist/types/src/nanotdf/NanoTDF.d.ts +1 -2
  36. package/dist/types/src/nanotdf/NanoTDF.d.ts.map +1 -1
  37. package/dist/types/src/nanotdf/encrypt-dataset.d.ts +1 -2
  38. package/dist/types/src/nanotdf/encrypt-dataset.d.ts.map +1 -1
  39. package/dist/types/src/nanotdf/encrypt.d.ts +1 -2
  40. package/dist/types/src/nanotdf/encrypt.d.ts.map +1 -1
  41. package/dist/types/src/nanotdf/helpers/getHkdfSalt.d.ts +1 -2
  42. package/dist/types/src/nanotdf/helpers/getHkdfSalt.d.ts.map +1 -1
  43. package/dist/types/src/nanotdf-crypto/digest.d.ts +1 -2
  44. package/dist/types/src/nanotdf-crypto/digest.d.ts.map +1 -1
  45. package/dist/types/src/nanotdf-crypto/pemPublicToCrypto.d.ts.map +1 -1
  46. package/dist/types/src/opentdf.d.ts +29 -5
  47. package/dist/types/src/opentdf.d.ts.map +1 -1
  48. package/dist/types/src/seekable.d.ts.map +1 -1
  49. package/dist/types/src/tdf/Policy.d.ts +4 -2
  50. package/dist/types/src/tdf/Policy.d.ts.map +1 -1
  51. package/dist/types/src/utils.d.ts.map +1 -1
  52. package/dist/types/src/version.d.ts +1 -1
  53. package/dist/types/tdf3/index.d.ts +1 -1
  54. package/dist/types/tdf3/index.d.ts.map +1 -1
  55. package/dist/types/tdf3/src/assertions.d.ts +1 -2
  56. package/dist/types/tdf3/src/assertions.d.ts.map +1 -1
  57. package/dist/types/tdf3/src/client/DecoratedReadableStream.d.ts +1 -1
  58. package/dist/types/tdf3/src/client/index.d.ts +1 -6
  59. package/dist/types/tdf3/src/client/index.d.ts.map +1 -1
  60. package/dist/types/tdf3/src/client/validation.d.ts.map +1 -1
  61. package/dist/types/tdf3/src/crypto/crypto-utils.d.ts.map +1 -1
  62. package/dist/types/tdf3/src/crypto/salt.d.ts +2 -0
  63. package/dist/types/tdf3/src/crypto/salt.d.ts.map +1 -0
  64. package/dist/types/tdf3/src/models/attribute.d.ts +16 -0
  65. package/dist/types/tdf3/src/models/attribute.d.ts.map +1 -0
  66. package/dist/types/tdf3/src/models/index.d.ts +1 -1
  67. package/dist/types/tdf3/src/models/index.d.ts.map +1 -1
  68. package/dist/types/tdf3/src/models/key-access.d.ts.map +1 -1
  69. package/dist/types/tdf3/src/models/payload.d.ts +1 -0
  70. package/dist/types/tdf3/src/models/payload.d.ts.map +1 -1
  71. package/dist/types/tdf3/src/models/policy.d.ts +1 -1
  72. package/dist/types/tdf3/src/models/policy.d.ts.map +1 -1
  73. package/dist/types/tdf3/src/tdf.d.ts +5 -9
  74. package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
  75. package/dist/web/src/access.js +1 -2
  76. package/dist/web/src/auth/auth.js +1 -1
  77. package/dist/web/src/encodings/base64.js +1 -1
  78. package/dist/web/src/index.js +2 -1
  79. package/dist/web/src/nanoclients.js +18 -11
  80. package/dist/web/src/nanotdf/Client.js +1 -1
  81. package/dist/web/src/nanotdf/NanoTDF.js +1 -1
  82. package/dist/web/src/nanotdf/encrypt-dataset.js +1 -1
  83. package/dist/web/src/nanotdf/encrypt.js +1 -1
  84. package/dist/web/src/nanotdf/helpers/getHkdfSalt.js +1 -1
  85. package/dist/web/src/nanotdf-crypto/digest.js +1 -1
  86. package/dist/web/src/nanotdf-crypto/pemPublicToCrypto.js +4 -27
  87. package/dist/web/src/opentdf.js +174 -44
  88. package/dist/web/src/tdf/Policy.js +13 -10
  89. package/dist/web/src/version.js +1 -1
  90. package/dist/web/tdf3/index.js +1 -1
  91. package/dist/web/tdf3/src/assertions.js +1 -1
  92. package/dist/web/tdf3/src/crypto/salt.js +9 -0
  93. package/dist/web/tdf3/src/models/attribute.js +2 -0
  94. package/dist/web/tdf3/src/models/index.js +2 -2
  95. package/dist/web/tdf3/src/models/key-access.js +3 -2
  96. package/dist/web/tdf3/src/tdf.js +7 -3
  97. package/package.json +12 -12
  98. package/src/access.ts +0 -1
  99. package/src/auth/auth.ts +2 -2
  100. package/src/encodings/base64.ts +1 -1
  101. package/src/index.ts +1 -0
  102. package/src/nanoclients.ts +24 -23
  103. package/src/nanotdf/Client.ts +2 -3
  104. package/src/nanotdf/NanoTDF.ts +1 -2
  105. package/src/nanotdf/encrypt-dataset.ts +1 -2
  106. package/src/nanotdf/encrypt.ts +1 -2
  107. package/src/nanotdf/helpers/getHkdfSalt.ts +1 -3
  108. package/src/nanotdf-crypto/digest.ts +1 -3
  109. package/src/nanotdf-crypto/pemPublicToCrypto.ts +3 -33
  110. package/src/opentdf.ts +249 -54
  111. package/src/tdf/Policy.ts +15 -9
  112. package/src/version.ts +1 -1
  113. package/tdf3/index.ts +2 -1
  114. package/tdf3/src/assertions.ts +2 -2
  115. package/tdf3/src/crypto/salt.ts +11 -0
  116. package/tdf3/src/models/attribute.ts +26 -0
  117. package/tdf3/src/models/index.ts +1 -1
  118. package/tdf3/src/models/key-access.ts +2 -1
  119. package/tdf3/src/models/payload.ts +1 -0
  120. package/tdf3/src/models/policy.ts +1 -1
  121. package/tdf3/src/tdf.ts +22 -13
  122. package/dist/cjs/src/tdf/PolicyObject.js +0 -3
  123. package/dist/cjs/src/tdf/TypedArray.js +0 -3
  124. package/dist/cjs/tdf3/src/models/attribute-set.js +0 -122
  125. package/dist/types/src/tdf/PolicyObject.d.ts +0 -10
  126. package/dist/types/src/tdf/PolicyObject.d.ts.map +0 -1
  127. package/dist/types/src/tdf/TypedArray.d.ts +0 -2
  128. package/dist/types/src/tdf/TypedArray.d.ts.map +0 -1
  129. package/dist/types/tdf3/src/models/attribute-set.d.ts +0 -65
  130. package/dist/types/tdf3/src/models/attribute-set.d.ts.map +0 -1
  131. package/dist/web/src/tdf/PolicyObject.js +0 -2
  132. package/dist/web/src/tdf/TypedArray.js +0 -2
  133. package/dist/web/tdf3/src/models/attribute-set.js +0 -118
  134. package/src/tdf/PolicyObject.ts +0 -11
  135. package/src/tdf/TypedArray.ts +0 -10
  136. 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 { type IntegrityAlgorithm } from '../tdf3/src/tdf.js';
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
- private tdf3Client: TDF3Client;
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(opts: CreateNanoTDFCollectionOptions): Promise<NanoTDFCollection> {
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
- * Decrypts a nanotdf object. Optionally, stores the collection header and its DEK.
344
- * @param ciphertext
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
- async read(opts: ReadOptions): Promise<DecoratedStream> {
381
+ open(opts: ReadOptions): TDFReader {
347
382
  opts = { ...this.defaultReadOptions, ...opts };
348
- const chunker = await fromSource(opts.source);
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
- const allowList = new OriginAllowList(opts.allowedKASEndpoints ?? [], opts.ignoreAllowlist);
353
- const oldStream = await this.tdf3Client.decrypt({
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
- const ciphertext = await chunker();
365
- const nanotdf = NanoTDF.from(ciphertext);
366
- const cachedDEK = this.rewrapCache.get(nanotdf.header.ephemeralPublicKey);
367
- if (cachedDEK) {
368
- const r: DecoratedStream = await streamify(decryptNanoTDF(cachedDEK, nanotdf));
369
- r.header = nanotdf.header;
370
- return r;
371
- }
372
- const nc = new Client({
373
- allowedKases: opts.allowedKASEndpoints,
374
- authProvider: this.authProvider,
375
- ignoreAllowList: opts.ignoreAllowlist,
376
- dpopEnabled: this.dpopEnabled,
377
- dpopKeys: this.dpopKeys,
378
- kasEndpoint: opts.allowedKASEndpoints?.[0] || 'https://disallow.all.invalid',
379
- });
380
- // TODO: The version number should be fetched from the API
381
- const version = '0.0.1';
382
- // Rewrap key on every request
383
- const dek = await nc.rewrapKey(
384
- nanotdf.header.toBuffer(),
385
- nanotdf.header.getKasRewrapUrl(),
386
- nanotdf.header.magicNumberVersion,
387
- version
388
- );
389
- if (!dek) {
390
- // These should have thrown already.
391
- throw new Error('internal: key rewrap failure');
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
- this.rewrapCache.set(nanotdf.header.ephemeralPublicKey, dek);
394
- const r: DecoratedStream = await streamify(decryptNanoTDF(dek, nanotdf));
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
- throw new InvalidFileError(`unsupported format; prefix not recognized ${prefix}`);
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
- this.rewrapCache.close();
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 NanoTDFCollection = {
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
- export class Policy {
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
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Exposes the released version number of the `@opentdf/sdk` package
3
3
  */
4
- export const version = '0.2.0';
4
+ export const version = '0.3.0';
5
5
 
6
6
  /**
7
7
  * A string name used to label requests as coming from this library client.
package/tdf3/index.ts CHANGED
@@ -90,7 +90,8 @@ export {
90
90
  type DecoratedStream,
91
91
  type Keys,
92
92
  type OpenTDFOptions,
93
- type NanoTDFCollection,
93
+ type NanoTDFCollectionWriter,
94
94
  type ReadOptions,
95
+ type TDFReader,
95
96
  OpenTDF,
96
97
  } from '../src/opentdf.js';
@@ -1,5 +1,5 @@
1
1
  import { canonicalizeEx } from 'json-canonicalize';
2
- import { type KeyLike, SignJWT, jwtVerify } from 'jose';
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: KeyLike | Uint8Array;
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
+ };
@@ -1,4 +1,4 @@
1
- export * from './attribute-set.js';
1
+ export * from './attribute.js';
2
2
  export * from './encryption-information.js';
3
3
  export * from './key-access.js';
4
4
  export * from './manifest.js';
@@ -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: new TextEncoder().encode('salt'),
48
+ hkdfSalt: await ztdfSalt,
48
49
  hkdfHash: 'SHA-256',
49
50
  });
50
51
  const iv = generateRandomNumber(12);
@@ -3,4 +3,5 @@ export type Payload = {
3
3
  url: string; // "0.payload"
4
4
  protocol: string; // "zip"
5
5
  isEncrypted: boolean; // true
6
+ mimeType?: string; // e.g. "text/plain"
6
7
  };
@@ -1,5 +1,5 @@
1
1
  import { ConfigurationError } from '../../../src/errors.js';
2
- import { AttributeObject } from './attribute-set.js';
2
+ import { type AttributeObject } from './attribute.js';
3
3
 
4
4
  export const CURRENT_VERSION = '1.1.0';
5
5
 
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: new TextEncoder().encode('salt'),
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
- const { manifest, zipReader, centralDirectory } = await loadTDFStream(cfg.chunker);
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=