@opentdf/sdk 0.14.0-rc.133 → 0.16.0

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 (64) hide show
  1. package/dist/cjs/src/index.js +17 -6
  2. package/dist/cjs/src/opentdf.js +4 -2
  3. package/dist/cjs/src/platform/authorization/entity-identifiers.js +3 -2
  4. package/dist/cjs/src/platform/authorization/resources.js +57 -0
  5. package/dist/cjs/src/version.js +1 -1
  6. package/dist/cjs/tdf3/src/ciphers/aes-gcm-cipher.js +11 -7
  7. package/dist/cjs/tdf3/src/ciphers/symmetric-cipher-base.js +1 -1
  8. package/dist/cjs/tdf3/src/client/builders.js +17 -1
  9. package/dist/cjs/tdf3/src/client/index.js +4 -2
  10. package/dist/cjs/tdf3/src/crypto/core/symmetric.js +29 -12
  11. package/dist/cjs/tdf3/src/models/encryption-information.js +1 -1
  12. package/dist/cjs/tdf3/src/tdf.js +199 -53
  13. package/dist/cjs/tdf3/src/utils/zip-reader.js +2 -2
  14. package/dist/types/src/index.d.ts +12 -1
  15. package/dist/types/src/index.d.ts.map +1 -1
  16. package/dist/types/src/opentdf.d.ts +10 -0
  17. package/dist/types/src/opentdf.d.ts.map +1 -1
  18. package/dist/types/src/platform/authorization/entity-identifiers.d.ts +2 -1
  19. package/dist/types/src/platform/authorization/entity-identifiers.d.ts.map +1 -1
  20. package/dist/types/src/platform/authorization/resources.d.ts +37 -0
  21. package/dist/types/src/platform/authorization/resources.d.ts.map +1 -0
  22. package/dist/types/src/version.d.ts +1 -1
  23. package/dist/types/tdf3/src/ciphers/aes-gcm-cipher.d.ts +1 -1
  24. package/dist/types/tdf3/src/ciphers/aes-gcm-cipher.d.ts.map +1 -1
  25. package/dist/types/tdf3/src/ciphers/symmetric-cipher-base.d.ts +1 -1
  26. package/dist/types/tdf3/src/ciphers/symmetric-cipher-base.d.ts.map +1 -1
  27. package/dist/types/tdf3/src/client/builders.d.ts +14 -0
  28. package/dist/types/tdf3/src/client/builders.d.ts.map +1 -1
  29. package/dist/types/tdf3/src/client/index.d.ts +1 -1
  30. package/dist/types/tdf3/src/client/index.d.ts.map +1 -1
  31. package/dist/types/tdf3/src/crypto/core/symmetric.d.ts +1 -0
  32. package/dist/types/tdf3/src/crypto/core/symmetric.d.ts.map +1 -1
  33. package/dist/types/tdf3/src/models/encryption-information.d.ts +1 -1
  34. package/dist/types/tdf3/src/models/encryption-information.d.ts.map +1 -1
  35. package/dist/types/tdf3/src/tdf.d.ts +20 -0
  36. package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
  37. package/dist/types/tdf3/src/utils/zip-reader.d.ts.map +1 -1
  38. package/dist/web/src/index.js +13 -2
  39. package/dist/web/src/opentdf.js +4 -2
  40. package/dist/web/src/platform/authorization/entity-identifiers.js +3 -2
  41. package/dist/web/src/platform/authorization/resources.js +53 -0
  42. package/dist/web/src/version.js +1 -1
  43. package/dist/web/tdf3/src/ciphers/aes-gcm-cipher.js +11 -7
  44. package/dist/web/tdf3/src/ciphers/symmetric-cipher-base.js +1 -1
  45. package/dist/web/tdf3/src/client/builders.js +17 -1
  46. package/dist/web/tdf3/src/client/index.js +4 -2
  47. package/dist/web/tdf3/src/crypto/core/symmetric.js +28 -12
  48. package/dist/web/tdf3/src/models/encryption-information.js +1 -1
  49. package/dist/web/tdf3/src/tdf.js +198 -53
  50. package/dist/web/tdf3/src/utils/zip-reader.js +2 -2
  51. package/package.json +2 -2
  52. package/src/index.ts +12 -7
  53. package/src/opentdf.ts +16 -0
  54. package/src/platform/authorization/entity-identifiers.ts +2 -1
  55. package/src/platform/authorization/resources.ts +59 -0
  56. package/src/version.ts +1 -1
  57. package/tdf3/src/ciphers/aes-gcm-cipher.ts +19 -14
  58. package/tdf3/src/ciphers/symmetric-cipher-base.ts +5 -1
  59. package/tdf3/src/client/builders.ts +20 -0
  60. package/tdf3/src/client/index.ts +4 -0
  61. package/tdf3/src/crypto/core/symmetric.ts +48 -14
  62. package/tdf3/src/models/encryption-information.ts +1 -1
  63. package/tdf3/src/tdf.ts +296 -79
  64. package/tdf3/src/utils/zip-reader.ts +1 -2
package/src/opentdf.ts CHANGED
@@ -153,6 +153,18 @@ export type ReadOptions = {
153
153
  /** If set, prevents more than this number of concurrent requests to the KAS. */
154
154
  concurrencyLimit?: number;
155
155
 
156
+ /**
157
+ * Maximum number of payload segments to fetch and decrypt per batch.
158
+ * Adjust together with `maxConcurrentSegmentBatches` for expected throughput.
159
+ */
160
+ segmentBatchSize?: number;
161
+
162
+ /**
163
+ * Maximum number of segment batches that may be fetched concurrently.
164
+ * Adjust together with `segmentBatchSize` for expected throughput.
165
+ */
166
+ maxConcurrentSegmentBatches?: number;
167
+
156
168
  /** Type of key to use for wrapping responses. */
157
169
  wrappingKeyAlgorithm?: KasPublicKeyAlgorithm;
158
170
  };
@@ -495,7 +507,9 @@ class ZTDFReader {
495
507
  async decrypt(): Promise<DecoratedStream> {
496
508
  const {
497
509
  assertionVerificationKeys,
510
+ maxConcurrentSegmentBatches,
498
511
  noVerify: noVerifyAssertions,
512
+ segmentBatchSize,
499
513
  wrappingKeyAlgorithm,
500
514
  } = this.opts;
501
515
 
@@ -534,7 +548,9 @@ class ZTDFReader {
534
548
  keyMiddleware: async (k) => k,
535
549
  progressHandler: this.client.clientConfig.progressHandler,
536
550
  assertionVerificationKeys,
551
+ maxConcurrentSegmentBatches,
537
552
  noVerifyAssertions,
553
+ segmentBatchSize,
538
554
  wrappingKeyAlgorithm,
539
555
  fulfillableObligations: this.opts.fulfillableObligationFQNs || [],
540
556
  },
@@ -35,7 +35,8 @@ import {
35
35
  * });
36
36
  *
37
37
  * // After
38
- * const eid = forEmail('jen@example.com');
38
+ * import { EntityIdentifiers } from '@opentdf/sdk';
39
+ * const eid = EntityIdentifiers.forEmail('jen@example.com');
39
40
  * ```
40
41
  */
41
42
 
@@ -0,0 +1,59 @@
1
+ import { create } from '@bufbuild/protobuf';
2
+ import {
3
+ type Resource,
4
+ ResourceSchema,
5
+ Resource_AttributeValuesSchema,
6
+ } from './v2/authorization_pb.js';
7
+
8
+ /**
9
+ * Convenience constructors for {@link Resource}, analogous to the
10
+ * {@link EntityIdentifiers} helpers for {@link EntityIdentifier}.
11
+ *
12
+ * Each function builds a complete `Resource` so callers avoid deeply nested
13
+ * object literals.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Before
18
+ * const resource = create(ResourceSchema, {
19
+ * resource: {
20
+ * case: 'attributeValues',
21
+ * value: create(Resource_AttributeValuesSchema, {
22
+ * fqns: ['https://example.com/attr/department/value/finance'],
23
+ * }),
24
+ * },
25
+ * });
26
+ *
27
+ * // After
28
+ * import { Resources } from '@opentdf/sdk';
29
+ * const resource = Resources.forAttributeValues('https://example.com/attr/department/value/finance');
30
+ * ```
31
+ */
32
+
33
+ /**
34
+ * Returns a Resource containing the given attribute value FQNs.
35
+ * This is the most common Resource variant, used when authorizing against
36
+ * attribute values attached to data (e.g. those on a TDF).
37
+ */
38
+ export function forAttributeValues(fqn: string, ...moreFqns: string[]): Resource {
39
+ const fqns = [fqn, ...moreFqns];
40
+ return create(ResourceSchema, {
41
+ resource: {
42
+ case: 'attributeValues',
43
+ value: create(Resource_AttributeValuesSchema, { fqns }),
44
+ },
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Returns a Resource that references a single registered resource value
50
+ * by its fully qualified name, as stored in platform policy.
51
+ */
52
+ export function forRegisteredResourceValueFqn(fqn: string): Resource {
53
+ return create(ResourceSchema, {
54
+ resource: {
55
+ case: 'registeredResourceValueFqn',
56
+ value: fqn,
57
+ },
58
+ });
59
+ }
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.14.0'; // x-release-please-version
4
+ export const version = '0.16.0'; // x-release-please-version
5
5
 
6
6
  /**
7
7
  * A string name used to label requests as coming from this library client.
@@ -1,6 +1,7 @@
1
1
  import { Binary } from '../binary.js';
2
2
  import { Algorithms } from './algorithms.js';
3
3
  import { SymmetricCipher } from './symmetric-cipher-base.js';
4
+ import { decryptBufferSource } from '../crypto/core/symmetric.js';
4
5
  import { concatUint8 } from '../utils/index.js';
5
6
 
6
7
  import {
@@ -16,20 +17,17 @@ const IV_LENGTH = 12;
16
17
  type ProcessGcmPayload = {
17
18
  payload: Binary;
18
19
  payloadIv: Binary;
19
- payloadAuthTag: Binary;
20
20
  };
21
21
  // Should this be a Binary, Buffer, or... both?
22
22
  function processGcmPayload(source: ArrayBuffer): ProcessGcmPayload {
23
23
  // Read the 12 byte IV from the beginning of the stream
24
24
  const payloadIv = Binary.fromArrayBuffer(source.slice(0, 12));
25
25
 
26
- // Slice the final 16 bytes of the buffer for the authentication tag
27
- const payloadAuthTag = Binary.fromArrayBuffer(source.slice(-16));
28
-
29
26
  return {
30
- payload: Binary.fromArrayBuffer(source.slice(12, -16)),
27
+ // WebCrypto AES-GCM expects ciphertext with the auth tag appended, so keep
28
+ // the tag attached instead of splitting and re-concatenating it later.
29
+ payload: Binary.fromArrayBuffer(source.slice(12)),
31
30
  payloadIv,
32
- payloadAuthTag,
33
31
  };
34
32
  }
35
33
 
@@ -64,18 +62,25 @@ export class AesGcmCipher extends SymmetricCipher {
64
62
  */
65
63
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
66
64
  override async decrypt(
67
- buffer: ArrayBuffer,
65
+ buffer: ArrayBuffer | Uint8Array,
68
66
  key: SymmetricKey,
69
67
  iv?: Binary
70
68
  ): Promise<DecryptResult> {
71
- const { payload, payloadIv, payloadAuthTag } = processGcmPayload(buffer);
69
+ const input = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
70
+
71
+ if (this.cryptoService.name === 'BrowserNativeCryptoService') {
72
+ return decryptBufferSource(
73
+ input.subarray(12),
74
+ key,
75
+ input.subarray(0, 12),
76
+ Algorithms.AES_256_GCM
77
+ );
78
+ }
72
79
 
73
- return this.cryptoService.decrypt(
74
- payload,
75
- key,
76
- payloadIv,
77
- Algorithms.AES_256_GCM,
78
- payloadAuthTag
80
+ const { payload, payloadIv } = processGcmPayload(
81
+ input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength)
79
82
  );
83
+
84
+ return this.cryptoService.decrypt(payload, key, payloadIv, Algorithms.AES_256_GCM);
80
85
  }
81
86
  }
@@ -37,5 +37,9 @@ export abstract class SymmetricCipher {
37
37
 
38
38
  abstract encrypt(payload: Binary, key: SymmetricKey, iv: Binary): Promise<EncryptResult>;
39
39
 
40
- abstract decrypt(payload: Uint8Array, key: SymmetricKey, iv?: Binary): Promise<DecryptResult>;
40
+ abstract decrypt(
41
+ payload: ArrayBuffer | Uint8Array,
42
+ key: SymmetricKey,
43
+ iv?: Binary
44
+ ): Promise<DecryptResult>;
41
45
  }
@@ -536,6 +536,8 @@ export type DecryptParams = {
536
536
  streamMiddleware?: DecryptStreamMiddleware;
537
537
  assertionVerificationKeys?: AssertionVerificationKeys;
538
538
  concurrencyLimit?: number;
539
+ segmentBatchSize?: number;
540
+ maxConcurrentSegmentBatches?: number;
539
541
  noVerifyAssertions?: boolean;
540
542
  wrappingKeyAlgorithm?: KasPublicKeyAlgorithm;
541
543
  fulfillableObligationFQNs?: string[];
@@ -725,6 +727,24 @@ class DecryptParamsBuilder {
725
727
  return this;
726
728
  }
727
729
 
730
+ /**
731
+ * Set the number of payload segments to fetch and decrypt per batch.
732
+ * Adjust together with `withMaxConcurrentSegmentBatches()` for expected throughput.
733
+ */
734
+ withSegmentBatchSize(size: number): DecryptParamsBuilder {
735
+ this._params.segmentBatchSize = size;
736
+ return this;
737
+ }
738
+
739
+ /**
740
+ * Set the maximum number of payload segment batches to fetch concurrently.
741
+ * Adjust together with `withSegmentBatchSize()` for expected throughput.
742
+ */
743
+ withMaxConcurrentSegmentBatches(limit: number): DecryptParamsBuilder {
744
+ this._params.maxConcurrentSegmentBatches = limit;
745
+ return this;
746
+ }
747
+
728
748
  /**
729
749
  * Generate a parameters object in the form expected by <code>{@link Client#decrypt|decrypt}</code>.
730
750
  * <br/><br/>
@@ -800,6 +800,8 @@ export class Client {
800
800
  assertionVerificationKeys,
801
801
  noVerifyAssertions,
802
802
  concurrencyLimit = 1,
803
+ maxConcurrentSegmentBatches,
804
+ segmentBatchSize,
803
805
  wrappingKeyAlgorithm,
804
806
  fulfillableObligationFQNs = [],
805
807
  }: DecryptParams): Promise<DecoratedReadableStream> {
@@ -836,7 +838,9 @@ export class Client {
836
838
  keyMiddleware,
837
839
  progressHandler: this.clientConfig.progressHandler,
838
840
  assertionVerificationKeys,
841
+ maxConcurrentSegmentBatches,
839
842
  noVerifyAssertions,
843
+ segmentBatchSize,
840
844
  wrappingKeyAlgorithm,
841
845
  fulfillableObligations: fulfillableObligationFQNs,
842
846
  })
@@ -13,6 +13,16 @@ import { unwrapSymmetricKey, wrapSymmetricKey } from './keys.js';
13
13
 
14
14
  const ENC_DEC_METHODS: KeyUsage[] = ['encrypt', 'decrypt'];
15
15
 
16
+ function asUint8ArrayView(buffer: BufferSource): Uint8Array {
17
+ if (buffer instanceof Uint8Array) {
18
+ return buffer;
19
+ }
20
+ if (ArrayBuffer.isView(buffer)) {
21
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
22
+ }
23
+ return new Uint8Array(buffer);
24
+ }
25
+
16
26
  /**
17
27
  * Generate a random symmetric key (opaque).
18
28
  * @param length - Key length in bytes (default 32 for AES-256)
@@ -61,7 +71,23 @@ export function decrypt(
61
71
  algorithm?: AlgorithmUrn,
62
72
  authTag?: Binary
63
73
  ): Promise<DecryptResult> {
64
- return _doDecrypt(payload, key, iv, algorithm, authTag);
74
+ return _doDecryptBufferSource(
75
+ payload.asArrayBuffer(),
76
+ key,
77
+ iv.asArrayBuffer(),
78
+ algorithm,
79
+ authTag?.asArrayBuffer()
80
+ );
81
+ }
82
+
83
+ export function decryptBufferSource(
84
+ payload: BufferSource,
85
+ key: SymmetricKey,
86
+ iv: BufferSource,
87
+ algorithm?: AlgorithmUrn,
88
+ authTag?: BufferSource
89
+ ): Promise<DecryptResult> {
90
+ return _doDecryptBufferSource(payload, key, iv, algorithm, authTag);
65
91
  }
66
92
 
67
93
  /**
@@ -115,32 +141,33 @@ async function _doEncrypt(
115
141
  };
116
142
  }
117
143
 
118
- async function _doDecrypt(
119
- payload: Binary,
144
+ async function _doDecryptBufferSource(
145
+ payload: BufferSource,
120
146
  key: SymmetricKey,
121
- iv: Binary,
147
+ iv: BufferSource,
122
148
  algorithm?: AlgorithmUrn,
123
- authTag?: Binary
149
+ authTag?: BufferSource
124
150
  ): Promise<DecryptResult> {
125
151
  console.assert(payload != null);
126
152
  console.assert(key != null);
127
153
  console.assert(iv != null);
128
154
 
129
- let payloadBuffer = payload.asArrayBuffer();
155
+ let payloadBuffer: BufferSource = payload;
130
156
 
131
157
  // Concat the the auth tag to the payload for decryption
132
158
  if (authTag) {
133
- const authTagBuffer = authTag.asArrayBuffer();
134
- const gcmPayload = new Uint8Array(payloadBuffer.byteLength + authTagBuffer.byteLength);
135
- gcmPayload.set(new Uint8Array(payloadBuffer), 0);
136
- gcmPayload.set(new Uint8Array(authTagBuffer), payloadBuffer.byteLength);
137
- payloadBuffer = gcmPayload.buffer;
159
+ const payloadBytes = asUint8ArrayView(payloadBuffer);
160
+ const authTagBytes = asUint8ArrayView(authTag);
161
+ const gcmPayload = new Uint8Array(payloadBytes.byteLength + authTagBytes.byteLength);
162
+ gcmPayload.set(payloadBytes, 0);
163
+ gcmPayload.set(authTagBytes, payloadBytes.byteLength);
164
+ payloadBuffer = gcmPayload;
138
165
  }
139
166
 
140
- const algoDomString = getSymmetricAlgoDomString(iv, algorithm);
167
+ const ivBytes = asUint8ArrayView(iv);
168
+ const algoDomString = getSymmetricAlgoDomStringFromIv(ivBytes, algorithm);
141
169
  const keyBytes = unwrapSymmetricKey(key);
142
170
  const importedKey = await _importKey(keyBytes, algoDomString);
143
- algoDomString.iv = iv.asArrayBuffer();
144
171
 
145
172
  const decrypted = await crypto.subtle
146
173
  .decrypt(algoDomString, importedKey, payloadBuffer)
@@ -168,6 +195,13 @@ function _importKey(keyBytes: Uint8Array, algorithm: AesCbcParams | AesGcmParams
168
195
  function getSymmetricAlgoDomString(
169
196
  iv: Binary,
170
197
  algorithm?: AlgorithmUrn
198
+ ): AesCbcParams | AesGcmParams {
199
+ return getSymmetricAlgoDomStringFromIv(asUint8ArrayView(iv.asArrayBuffer()), algorithm);
200
+ }
201
+
202
+ function getSymmetricAlgoDomStringFromIv(
203
+ iv: Uint8Array,
204
+ algorithm?: AlgorithmUrn
171
205
  ): AesCbcParams | AesGcmParams {
172
206
  let nativeAlgorithm = 'AES-CBC';
173
207
  if (algorithm === Algorithms.AES_256_GCM) {
@@ -176,7 +210,7 @@ function getSymmetricAlgoDomString(
176
210
 
177
211
  return {
178
212
  name: nativeAlgorithm,
179
- iv: iv.asArrayBuffer(),
213
+ iv,
180
214
  };
181
215
  }
182
216
 
@@ -72,7 +72,7 @@ export class SplitKey {
72
72
  return this.cipher.encrypt(contentBinary, key, ivBinary);
73
73
  }
74
74
 
75
- async decrypt(content: Uint8Array, key: SymmetricKey): Promise<DecryptResult> {
75
+ async decrypt(content: ArrayBuffer | Uint8Array, key: SymmetricKey): Promise<DecryptResult> {
76
76
  return this.cipher.decrypt(content, key);
77
77
  }
78
78