@opentdf/sdk 0.5.0-beta.47 → 0.5.0-beta.49
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/access-rpc.js +55 -3
- package/dist/cjs/src/nanotdf/Client.js +43 -43
- package/dist/cjs/src/utils.js +30 -1
- package/dist/cjs/tdf3/src/tdf.js +82 -28
- package/dist/types/src/access/access-rpc.d.ts +2 -0
- package/dist/types/src/access/access-rpc.d.ts.map +1 -1
- package/dist/types/src/nanotdf/Client.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +4 -0
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/tdf3/src/tdf.d.ts +0 -4
- package/dist/types/tdf3/src/tdf.d.ts.map +1 -1
- package/dist/web/src/access/access-rpc.js +54 -4
- package/dist/web/src/nanotdf/Client.js +44 -11
- package/dist/web/src/utils.js +29 -1
- package/dist/web/tdf3/src/tdf.js +83 -29
- package/package.json +1 -1
- package/src/access/access-rpc.ts +66 -3
- package/src/nanotdf/Client.ts +52 -9
- package/src/utils.ts +34 -1
- package/tdf3/src/tdf.ts +102 -39
package/src/nanotdf/Client.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { create, toJsonString } from '@bufbuild/protobuf';
|
|
2
|
+
import {
|
|
3
|
+
UnsignedRewrapRequest_WithPolicyRequestSchema,
|
|
4
|
+
UnsignedRewrapRequestSchema,
|
|
5
|
+
} from '../platform/kas/kas_pb.js';
|
|
2
6
|
import { generateKeyPair, keyAgreement } from '../nanotdf-crypto/index.js';
|
|
3
7
|
import getHkdfSalt from './helpers/getHkdfSalt.js';
|
|
4
8
|
import DefaultParams from './models/DefaultParams.js';
|
|
@@ -8,13 +12,16 @@ import {
|
|
|
8
12
|
KasPublicKeyInfo,
|
|
9
13
|
OriginAllowList,
|
|
10
14
|
} from '../access.js';
|
|
15
|
+
import { handleRpcRewrapErrorString } from '../../src/access/access-rpc.js';
|
|
11
16
|
import { AuthProvider, isAuthProvider, reqSignature } from '../auth/providers.js';
|
|
12
17
|
import { ConfigurationError, DecryptError, TdfError, UnsafeUrlError } from '../errors.js';
|
|
13
18
|
import {
|
|
14
19
|
cryptoPublicToPem,
|
|
15
20
|
getRequiredObligationFQNs,
|
|
16
21
|
pemToCryptoPublicKey,
|
|
22
|
+
upgradeRewrapResponseV1,
|
|
17
23
|
validateSecureUrl,
|
|
24
|
+
getPlatformUrlFromKasEndpoint,
|
|
18
25
|
} from '../utils.js';
|
|
19
26
|
|
|
20
27
|
export interface ClientConfig {
|
|
@@ -260,18 +267,35 @@ export default class Client {
|
|
|
260
267
|
throw new ConfigurationError('Signer key has not been set or generated');
|
|
261
268
|
}
|
|
262
269
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
const unsignedRequest = create(UnsignedRewrapRequestSchema, {
|
|
271
|
+
clientPublicKey: await cryptoPublicToPem(ephemeralKeyPair.publicKey),
|
|
272
|
+
requests: [
|
|
273
|
+
create(UnsignedRewrapRequest_WithPolicyRequestSchema, {
|
|
274
|
+
keyAccessObjects: [
|
|
275
|
+
{
|
|
276
|
+
keyAccessObjectId: 'kao-0',
|
|
277
|
+
keyAccessObject: {
|
|
278
|
+
header: new Uint8Array(nanoTdfHeader),
|
|
279
|
+
kasUrl: '',
|
|
280
|
+
protocol: Client.KAS_PROTOCOL,
|
|
281
|
+
keyType: Client.KEY_ACCESS_REMOTE,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
algorithm: DefaultParams.defaultECAlgorithm,
|
|
286
|
+
}),
|
|
287
|
+
],
|
|
266
288
|
keyAccess: {
|
|
267
|
-
|
|
268
|
-
|
|
289
|
+
header: new Uint8Array(nanoTdfHeader),
|
|
290
|
+
kasUrl: '',
|
|
269
291
|
protocol: Client.KAS_PROTOCOL,
|
|
270
|
-
|
|
292
|
+
keyType: Client.KEY_ACCESS_REMOTE,
|
|
271
293
|
},
|
|
272
|
-
|
|
294
|
+
algorithm: DefaultParams.defaultECAlgorithm,
|
|
273
295
|
});
|
|
274
296
|
|
|
297
|
+
const requestBodyStr = toJsonString(UnsignedRewrapRequestSchema, unsignedRequest);
|
|
298
|
+
|
|
275
299
|
const jwtPayload = { requestBody: requestBodyStr };
|
|
276
300
|
|
|
277
301
|
const signedRequestToken = await reqSignature(jwtPayload, requestSignerKeyPair.privateKey, {
|
|
@@ -285,9 +309,28 @@ export default class Client {
|
|
|
285
309
|
this.authProvider,
|
|
286
310
|
this.fulfillableObligationFQNs
|
|
287
311
|
);
|
|
312
|
+
upgradeRewrapResponseV1(rewrapResp);
|
|
313
|
+
|
|
314
|
+
// Assume only one response and one result for now (V1 style)
|
|
315
|
+
const result = rewrapResp.responses[0].results[0];
|
|
316
|
+
let entityWrappedKey: Uint8Array<ArrayBufferLike>;
|
|
317
|
+
switch (result.result.case) {
|
|
318
|
+
case 'kasWrappedKey': {
|
|
319
|
+
entityWrappedKey = result.result.value;
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 'error': {
|
|
323
|
+
handleRpcRewrapErrorString(
|
|
324
|
+
result.result.value,
|
|
325
|
+
getPlatformUrlFromKasEndpoint(kasRewrapUrl)
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
default: {
|
|
329
|
+
throw new DecryptError('KAS rewrap response missing wrapped key');
|
|
330
|
+
}
|
|
331
|
+
}
|
|
288
332
|
|
|
289
333
|
// Extract the iv and ciphertext
|
|
290
|
-
const entityWrappedKey = rewrapResp.entityWrappedKey;
|
|
291
334
|
const ivLength =
|
|
292
335
|
clientVersion == Client.SDK_INITIAL_RELEASE ? Client.INITIAL_RELEASE_IV_SIZE : Client.IV_SIZE;
|
|
293
336
|
const iv = entityWrappedKey.subarray(0, ivLength);
|
package/src/utils.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { exportSPKI, importX509 } from 'jose';
|
|
|
3
3
|
import { base64 } from './encodings/index.js';
|
|
4
4
|
import { pemCertToCrypto, pemPublicToCrypto } from './nanotdf-crypto/pemPublicToCrypto.js';
|
|
5
5
|
import { ConfigurationError } from './errors.js';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
RewrapResponse,
|
|
8
|
+
PolicyRewrapResultSchema,
|
|
9
|
+
KeyAccessRewrapResultSchema,
|
|
10
|
+
} from './platform/kas/kas_pb.js';
|
|
11
|
+
import { create } from '@bufbuild/protobuf';
|
|
7
12
|
import { ConnectError } from '@connectrpc/connect';
|
|
8
13
|
|
|
9
14
|
const REQUIRED_OBLIGATIONS_METADATA_KEY = 'X-Required-Obligations';
|
|
@@ -255,3 +260,31 @@ export function getRequiredObligationFQNs(response: RewrapResponse) {
|
|
|
255
260
|
|
|
256
261
|
return [...requiredObligations.values()];
|
|
257
262
|
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Upgrades a RewrapResponse from v1 format to v2.
|
|
266
|
+
*/
|
|
267
|
+
export function upgradeRewrapResponseV1(response: RewrapResponse) {
|
|
268
|
+
if (response.responses.length > 0) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (response.entityWrappedKey.length === 0) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
response.responses = [
|
|
276
|
+
create(PolicyRewrapResultSchema, {
|
|
277
|
+
policyId: 'policy',
|
|
278
|
+
results: [
|
|
279
|
+
create(KeyAccessRewrapResultSchema, {
|
|
280
|
+
keyAccessObjectId: 'kao-0',
|
|
281
|
+
status: 'permit',
|
|
282
|
+
result: {
|
|
283
|
+
case: 'kasWrappedKey',
|
|
284
|
+
value: response.entityWrappedKey,
|
|
285
|
+
},
|
|
286
|
+
}),
|
|
287
|
+
],
|
|
288
|
+
}),
|
|
289
|
+
];
|
|
290
|
+
}
|
package/tdf3/src/tdf.ts
CHANGED
|
@@ -8,7 +8,16 @@ import {
|
|
|
8
8
|
fetchWrappedKey,
|
|
9
9
|
publicKeyAlgorithmToJwa,
|
|
10
10
|
} from '../../src/access.js';
|
|
11
|
+
import { create, toJsonString } from '@bufbuild/protobuf';
|
|
12
|
+
import {
|
|
13
|
+
KeyAccessSchema,
|
|
14
|
+
UnsignedRewrapRequestSchema,
|
|
15
|
+
UnsignedRewrapRequest_WithPolicyRequestSchema,
|
|
16
|
+
UnsignedRewrapRequest_WithPolicySchema,
|
|
17
|
+
UnsignedRewrapRequest_WithKeyAccessObjectSchema,
|
|
18
|
+
} from '../../src/platform/kas/kas_pb.js';
|
|
11
19
|
import { type AuthProvider, reqSignature } from '../../src/auth/auth.js';
|
|
20
|
+
import { handleRpcRewrapErrorString } from '../../src/access/access-rpc.js';
|
|
12
21
|
import { allPool, anyPool } from '../../src/concurrency.js';
|
|
13
22
|
import { base64, hex } from '../../src/encodings/index.js';
|
|
14
23
|
import {
|
|
@@ -55,7 +64,11 @@ import { ZipReader, ZipWriter, keyMerge, concatUint8, buffToString } from './uti
|
|
|
55
64
|
import { CentralDirectory } from './utils/zip-reader.js';
|
|
56
65
|
import { ztdfSalt } from './crypto/salt.js';
|
|
57
66
|
import { Payload } from './models/payload.js';
|
|
58
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
getRequiredObligationFQNs,
|
|
69
|
+
upgradeRewrapResponseV1,
|
|
70
|
+
getPlatformUrlFromKasEndpoint,
|
|
71
|
+
} from '../../src/utils.js';
|
|
59
72
|
|
|
60
73
|
// TODO: input validation on manifest JSON
|
|
61
74
|
const DEFAULT_SEGMENT_SIZE = 1024 * 1024;
|
|
@@ -189,11 +202,6 @@ export type RewrapRequest = {
|
|
|
189
202
|
|
|
190
203
|
export type KasPublicKeyFormat = 'pkcs8' | 'jwks';
|
|
191
204
|
|
|
192
|
-
export type RewrapResponse = {
|
|
193
|
-
entityWrappedKey: string;
|
|
194
|
-
sessionPublicKey: string;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
205
|
/**
|
|
198
206
|
* If we have KAS url but not public key we can fetch it from KAS, fetching
|
|
199
207
|
* the value from `${kas}/kas_public_key`.
|
|
@@ -783,13 +791,50 @@ async function unwrapKey({
|
|
|
783
791
|
|
|
784
792
|
const clientPublicKey = ephemeralEncryptionKeys.publicKey;
|
|
785
793
|
|
|
786
|
-
|
|
794
|
+
// Convert keySplitInfo to protobuf KeyAccess
|
|
795
|
+
const keyAccessProto = create(KeyAccessSchema, {
|
|
796
|
+
...(keySplitInfo.type && { keyType: keySplitInfo.type }),
|
|
797
|
+
...(keySplitInfo.url && { kasUrl: keySplitInfo.url }),
|
|
798
|
+
...(keySplitInfo.protocol && { protocol: keySplitInfo.protocol }),
|
|
799
|
+
...(keySplitInfo.wrappedKey && {
|
|
800
|
+
wrappedKey: new Uint8Array(base64.decodeArrayBuffer(keySplitInfo.wrappedKey)),
|
|
801
|
+
}),
|
|
802
|
+
...(keySplitInfo.policyBinding && { policyBinding: keySplitInfo.policyBinding }),
|
|
803
|
+
...(keySplitInfo.kid && { kid: keySplitInfo.kid }),
|
|
804
|
+
...(keySplitInfo.sid && { splitId: keySplitInfo.sid }),
|
|
805
|
+
...(keySplitInfo.encryptedMetadata && { encryptedMetadata: keySplitInfo.encryptedMetadata }),
|
|
806
|
+
...(keySplitInfo.ephemeralPublicKey && {
|
|
807
|
+
ephemeralPublicKey: keySplitInfo.ephemeralPublicKey,
|
|
808
|
+
}),
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// Create the protobuf request
|
|
812
|
+
const unsignedRequest = create(UnsignedRewrapRequestSchema, {
|
|
813
|
+
clientPublicKey,
|
|
814
|
+
requests: [
|
|
815
|
+
create(UnsignedRewrapRequest_WithPolicyRequestSchema, {
|
|
816
|
+
keyAccessObjects: [
|
|
817
|
+
create(UnsignedRewrapRequest_WithKeyAccessObjectSchema, {
|
|
818
|
+
keyAccessObjectId: 'kao-0',
|
|
819
|
+
keyAccessObject: keyAccessProto,
|
|
820
|
+
}),
|
|
821
|
+
],
|
|
822
|
+
...(manifest.encryptionInformation.policy && {
|
|
823
|
+
policy: create(UnsignedRewrapRequest_WithPolicySchema, {
|
|
824
|
+
id: 'policy',
|
|
825
|
+
body: manifest.encryptionInformation.policy,
|
|
826
|
+
}),
|
|
827
|
+
}),
|
|
828
|
+
}),
|
|
829
|
+
],
|
|
830
|
+
// include deprecated fields for backward compatibility
|
|
787
831
|
algorithm: 'RS256',
|
|
788
|
-
keyAccess:
|
|
832
|
+
keyAccess: keyAccessProto,
|
|
789
833
|
policy: manifest.encryptionInformation.policy,
|
|
790
|
-
clientPublicKey,
|
|
791
834
|
});
|
|
792
835
|
|
|
836
|
+
const requestBodyStr = toJsonString(UnsignedRewrapRequestSchema, unsignedRequest);
|
|
837
|
+
|
|
793
838
|
const jwtPayload = { requestBody: requestBodyStr };
|
|
794
839
|
const signedRequestToken = await reqSignature(jwtPayload, dpopKeys.privateKey);
|
|
795
840
|
|
|
@@ -799,39 +844,57 @@ async function unwrapKey({
|
|
|
799
844
|
authProvider,
|
|
800
845
|
fulfillableObligations
|
|
801
846
|
);
|
|
802
|
-
|
|
847
|
+
upgradeRewrapResponseV1(rewrapResp);
|
|
848
|
+
const { sessionPublicKey } = rewrapResp;
|
|
803
849
|
const requiredObligations = getRequiredObligationFQNs(rewrapResp);
|
|
850
|
+
// Assume only one response and one result for now (V1 style)
|
|
851
|
+
const result = rewrapResp.responses[0].results[0];
|
|
852
|
+
const metadata = result.metadata;
|
|
853
|
+
// Handle the different cases of result.result
|
|
854
|
+
switch (result.result.case) {
|
|
855
|
+
case 'kasWrappedKey': {
|
|
856
|
+
const entityWrappedKey = result.result.value;
|
|
857
|
+
|
|
858
|
+
if (wrappingKeyAlgorithm === 'ec:secp256r1') {
|
|
859
|
+
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
|
|
860
|
+
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
|
|
861
|
+
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
|
|
862
|
+
hkdfSalt: await ztdfSalt,
|
|
863
|
+
hkdfHash: 'SHA-256',
|
|
864
|
+
});
|
|
865
|
+
const wrappedKeyAndNonce = entityWrappedKey;
|
|
866
|
+
const iv = wrappedKeyAndNonce.slice(0, 12);
|
|
867
|
+
const wrappedKey = wrappedKeyAndNonce.slice(12);
|
|
804
868
|
|
|
805
|
-
|
|
806
|
-
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
|
|
807
|
-
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
|
|
808
|
-
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
|
|
809
|
-
hkdfSalt: await ztdfSalt,
|
|
810
|
-
hkdfHash: 'SHA-256',
|
|
811
|
-
});
|
|
812
|
-
const wrappedKeyAndNonce = entityWrappedKey;
|
|
813
|
-
const iv = wrappedKeyAndNonce.slice(0, 12);
|
|
814
|
-
const wrappedKey = wrappedKeyAndNonce.slice(12);
|
|
815
|
-
|
|
816
|
-
const dek = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, kek, wrappedKey);
|
|
817
|
-
|
|
818
|
-
return {
|
|
819
|
-
key: new Uint8Array(dek),
|
|
820
|
-
metadata,
|
|
821
|
-
requiredObligations,
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
const key = Binary.fromArrayBuffer(entityWrappedKey);
|
|
825
|
-
const decryptedKeyBinary = await cryptoService.decryptWithPrivateKey(
|
|
826
|
-
key,
|
|
827
|
-
ephemeralEncryptionKeys.privateKey
|
|
828
|
-
);
|
|
869
|
+
const dek = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, kek, wrappedKey);
|
|
829
870
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
871
|
+
return {
|
|
872
|
+
key: new Uint8Array(dek),
|
|
873
|
+
metadata,
|
|
874
|
+
requiredObligations,
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
const key = Binary.fromArrayBuffer(entityWrappedKey);
|
|
878
|
+
const decryptedKeyBinary = await cryptoService.decryptWithPrivateKey(
|
|
879
|
+
key,
|
|
880
|
+
ephemeralEncryptionKeys.privateKey
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
return {
|
|
884
|
+
key: new Uint8Array(decryptedKeyBinary.asByteArray()),
|
|
885
|
+
metadata,
|
|
886
|
+
requiredObligations,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
case 'error': {
|
|
891
|
+
handleRpcRewrapErrorString(result.result.value, getPlatformUrlFromKasEndpoint(url));
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
default: {
|
|
895
|
+
throw new DecryptError('KAS rewrap response missing wrapped key');
|
|
896
|
+
}
|
|
897
|
+
}
|
|
835
898
|
}
|
|
836
899
|
|
|
837
900
|
let poolSize = 1;
|