@opentdf/sdk 0.2.0 → 0.3.0-beta.2029
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/auth/auth.js +1 -1
- package/dist/cjs/src/encodings/base64.js +1 -1
- package/dist/cjs/src/nanoclients.js +5 -5
- 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/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/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/nanoclients.d.ts +9 -10
- 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/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/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/key-access.d.ts.map +1 -1
- package/dist/types/tdf3/src/tdf.d.ts +4 -2
- package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
- package/dist/web/src/auth/auth.js +1 -1
- package/dist/web/src/encodings/base64.js +1 -1
- package/dist/web/src/nanoclients.js +5 -5
- 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/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/key-access.js +3 -2
- package/dist/web/tdf3/src/tdf.js +7 -3
- package/package.json +12 -12
- package/src/auth/auth.ts +2 -2
- package/src/encodings/base64.ts +1 -1
- package/src/nanoclients.ts +9 -16
- 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/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/key-access.ts +2 -1
- package/tdf3/src/tdf.ts +19 -5
- package/dist/cjs/src/tdf/TypedArray.js +0 -3
- package/dist/types/src/tdf/TypedArray.d.ts +0 -2
- package/dist/types/src/tdf/TypedArray.d.ts.map +0 -1
- package/dist/web/src/tdf/TypedArray.js +0 -2
- package/src/tdf/TypedArray.ts +0 -10
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 { PolicyObject } from './tdf/PolicyObject.js';
|
|
33
|
+
import PolicyType from './nanotdf/enum/PolicyTypeEnum.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 PolicyObject;
|
|
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 PolicyObject;
|
|
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/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();
|
|
@@ -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
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
import { unsigned } from './utils/buffer-crc32.js';
|
|
55
55
|
import { ZipReader, ZipWriter, keyMerge, concatUint8 } from './utils/index.js';
|
|
56
56
|
import { CentralDirectory } from './utils/zip-reader.js';
|
|
57
|
+
import { ztdfSalt } from './crypto/salt.js';
|
|
57
58
|
|
|
58
59
|
// TODO: input validation on manifest JSON
|
|
59
60
|
const DEFAULT_SEGMENT_SIZE = 1024 * 1024;
|
|
@@ -596,10 +597,14 @@ export async function writeStream(cfg: EncryptConfiguration): Promise<DecoratedR
|
|
|
596
597
|
}
|
|
597
598
|
}
|
|
598
599
|
|
|
600
|
+
export type InspectedTDFOverview = {
|
|
601
|
+
manifest: Manifest;
|
|
602
|
+
zipReader: ZipReader;
|
|
603
|
+
centralDirectory: CentralDirectory[];
|
|
604
|
+
};
|
|
605
|
+
|
|
599
606
|
// 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[] }> {
|
|
607
|
+
export async function loadTDFStream(chunker: Chunker): Promise<InspectedTDFOverview> {
|
|
603
608
|
const zipReader = new ZipReader(chunker);
|
|
604
609
|
const centralDirectory = await zipReader.getCentralDirectory();
|
|
605
610
|
const manifest = await zipReader.getManifest(centralDirectory, '0.manifest.json');
|
|
@@ -707,7 +712,7 @@ async function unwrapKey({
|
|
|
707
712
|
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
|
|
708
713
|
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
|
|
709
714
|
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
|
|
710
|
-
hkdfSalt:
|
|
715
|
+
hkdfSalt: await ztdfSalt,
|
|
711
716
|
hkdfHash: 'SHA-256',
|
|
712
717
|
});
|
|
713
718
|
const wrappedKeyAndNonce = base64.decodeArrayBuffer(entityWrappedKey);
|
|
@@ -919,6 +924,14 @@ export async function sliceAndDecrypt({
|
|
|
919
924
|
}
|
|
920
925
|
|
|
921
926
|
export async function readStream(cfg: DecryptConfiguration) {
|
|
927
|
+
const overview = await loadTDFStream(cfg.chunker);
|
|
928
|
+
return decryptStreamFrom(cfg, overview);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export async function decryptStreamFrom(
|
|
932
|
+
cfg: DecryptConfiguration,
|
|
933
|
+
{ manifest, zipReader, centralDirectory }: InspectedTDFOverview
|
|
934
|
+
) {
|
|
922
935
|
let { allowList } = cfg;
|
|
923
936
|
if (!allowList) {
|
|
924
937
|
if (!cfg.allowedKases) {
|
|
@@ -926,10 +939,11 @@ export async function readStream(cfg: DecryptConfiguration) {
|
|
|
926
939
|
}
|
|
927
940
|
allowList = new OriginAllowList(cfg.allowedKases);
|
|
928
941
|
}
|
|
929
|
-
|
|
942
|
+
|
|
930
943
|
if (!manifest) {
|
|
931
944
|
throw new InvalidFileError('Missing manifest data');
|
|
932
945
|
}
|
|
946
|
+
|
|
933
947
|
cfg.keyMiddleware ??= async (key) => key;
|
|
934
948
|
|
|
935
949
|
const {
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVHlwZWRBcnJheS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy90ZGYvVHlwZWRBcnJheS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TypedArray.d.ts","sourceRoot":"","sources":["../../../../src/tdf/TypedArray.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,UAAU,GACV,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,GACX,iBAAiB,GACjB,YAAY,GACZ,YAAY,CAAC"}
|