@opentdf/sdk 0.5.0-rc.44 → 0.6.0-beta.52
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 +57 -3
- package/dist/cjs/src/errors.js +6 -3
- package/dist/cjs/src/index.js +11 -2
- package/dist/cjs/src/nanotdf/Client.js +50 -44
- package/dist/cjs/src/utils.js +31 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/tdf3/src/tdf.js +88 -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/errors.d.ts +2 -0
- package/dist/types/src/errors.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/nanotdf/Client.d.ts.map +1 -1
- package/dist/types/src/utils.d.ts +5 -0
- package/dist/types/src/utils.d.ts.map +1 -1
- package/dist/types/src/version.d.ts +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 +56 -4
- package/dist/web/src/errors.js +6 -3
- package/dist/web/src/index.js +2 -1
- package/dist/web/src/nanotdf/Client.js +51 -12
- package/dist/web/src/utils.js +30 -1
- package/dist/web/src/version.js +1 -1
- package/dist/web/tdf3/src/tdf.js +89 -29
- package/package.json +1 -1
- package/src/access/access-rpc.ts +75 -3
- package/src/errors.ts +8 -0
- package/src/index.ts +10 -0
- package/src/nanotdf/Client.ts +62 -10
- package/src/utils.ts +35 -1
- package/src/version.ts +1 -1
- package/tdf3/src/tdf.ts +112 -39
package/src/access/access-rpc.ts
CHANGED
|
@@ -8,7 +8,14 @@ import {
|
|
|
8
8
|
} from '../access.js';
|
|
9
9
|
|
|
10
10
|
import { type AuthProvider } from '../auth/auth.js';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
ConfigurationError,
|
|
13
|
+
InvalidFileError,
|
|
14
|
+
NetworkError,
|
|
15
|
+
PermissionDeniedError,
|
|
16
|
+
ServiceError,
|
|
17
|
+
UnauthenticatedError,
|
|
18
|
+
} from '../errors.js';
|
|
12
19
|
import { PlatformClient } from '../platform.js';
|
|
13
20
|
import { RewrapResponse } from '../platform/kas/kas_pb.js';
|
|
14
21
|
import { ListKeyAccessServersResponse } from '../platform/policy/kasregistry/key_access_server_registry_pb.js';
|
|
@@ -19,6 +26,7 @@ import {
|
|
|
19
26
|
validateSecureUrl,
|
|
20
27
|
} from '../utils.js';
|
|
21
28
|
import { X_REWRAP_ADDITIONAL_CONTEXT } from './constants.js';
|
|
29
|
+
import { ConnectError, Code } from '@connectrpc/connect';
|
|
22
30
|
|
|
23
31
|
/**
|
|
24
32
|
* Get a rewrapped access key to the document, if possible
|
|
@@ -42,11 +50,75 @@ export async function fetchWrappedKey(
|
|
|
42
50
|
[X_REWRAP_ADDITIONAL_CONTEXT]: rewrapAdditionalContextHeader,
|
|
43
51
|
};
|
|
44
52
|
}
|
|
53
|
+
let response: RewrapResponse;
|
|
45
54
|
try {
|
|
46
|
-
|
|
55
|
+
response = await platform.v1.access.rewrap({ signedRequestToken }, options);
|
|
47
56
|
} catch (e) {
|
|
48
|
-
|
|
57
|
+
handleRpcRewrapError(e, platformUrl);
|
|
58
|
+
}
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function handleRpcRewrapError(e: unknown, platformUrl: string): never {
|
|
63
|
+
if (e instanceof ConnectError) {
|
|
64
|
+
console.log('Error is a ConnectError with code:', e.code);
|
|
65
|
+
switch (e.code) {
|
|
66
|
+
case Code.InvalidArgument: // 400 Bad Request
|
|
67
|
+
throw new InvalidFileError(`400 for [${platformUrl}]: rewrap bad request [${e.message}]`);
|
|
68
|
+
case Code.PermissionDenied: // 403 Forbidden
|
|
69
|
+
throw new PermissionDeniedError(`403 for [${platformUrl}]; rewrap permission denied`);
|
|
70
|
+
case Code.Unauthenticated: // 401 Unauthorized
|
|
71
|
+
throw new UnauthenticatedError(`401 for [${platformUrl}]; rewrap auth failure`);
|
|
72
|
+
case Code.Internal:
|
|
73
|
+
case Code.Unimplemented:
|
|
74
|
+
case Code.DataLoss:
|
|
75
|
+
case Code.Unknown:
|
|
76
|
+
case Code.DeadlineExceeded:
|
|
77
|
+
case Code.Unavailable: // >=500 Server Error
|
|
78
|
+
throw new ServiceError(
|
|
79
|
+
`${e.code} for [${platformUrl}]: rewrap failure due to service error [${e.message}]`
|
|
80
|
+
);
|
|
81
|
+
default:
|
|
82
|
+
throw new NetworkError(`[${platformUrl}] [Rewrap] ${e.message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw new NetworkError(`[${platformUrl}] [Rewrap] ${extractRpcErrorMessage(e)}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function handleRpcRewrapErrorString(
|
|
89
|
+
e: string,
|
|
90
|
+
platformUrl: string,
|
|
91
|
+
requiredObligations?: string[]
|
|
92
|
+
): never {
|
|
93
|
+
if (e.includes(Code[Code.InvalidArgument])) {
|
|
94
|
+
// 400 Bad Request
|
|
95
|
+
throw new InvalidFileError(`400 for [${platformUrl}]: rewrap bad request [${e}]`);
|
|
96
|
+
}
|
|
97
|
+
if (e.includes(Code[Code.PermissionDenied])) {
|
|
98
|
+
if (requiredObligations && requiredObligations.length > 0) {
|
|
99
|
+
throw new PermissionDeniedError(
|
|
100
|
+
`403 for [${platformUrl}]; rewrap permission denied`,
|
|
101
|
+
requiredObligations
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
throw new PermissionDeniedError(`403 for [${platformUrl}]; rewrap permission denied`);
|
|
105
|
+
}
|
|
106
|
+
if (e.includes(Code[Code.Unauthenticated])) {
|
|
107
|
+
// 401 Unauthorized
|
|
108
|
+
throw new UnauthenticatedError(`401 for [${platformUrl}]; rewrap auth failure`);
|
|
109
|
+
}
|
|
110
|
+
if (
|
|
111
|
+
e.includes(Code[Code.Internal]) ||
|
|
112
|
+
e.includes(Code[Code.Unimplemented]) ||
|
|
113
|
+
e.includes(Code[Code.DataLoss]) ||
|
|
114
|
+
e.includes(Code[Code.Unknown]) ||
|
|
115
|
+
e.includes(Code[Code.DeadlineExceeded]) ||
|
|
116
|
+
e.includes(Code[Code.Unavailable])
|
|
117
|
+
) {
|
|
118
|
+
// >=500
|
|
119
|
+
throw new ServiceError(`500+ [${platformUrl}]: rewrap failure due to service error [${e}]`);
|
|
49
120
|
}
|
|
121
|
+
throw new NetworkError(`[${platformUrl}] [Rewrap] ${e}`);
|
|
50
122
|
}
|
|
51
123
|
|
|
52
124
|
export async function fetchKeyAccessServers(
|
package/src/errors.ts
CHANGED
|
@@ -103,6 +103,14 @@ export class UnauthenticatedError extends TdfError {
|
|
|
103
103
|
/** Authorization failure (403) */
|
|
104
104
|
export class PermissionDeniedError extends TdfError {
|
|
105
105
|
override name = 'PermissionDeniedError';
|
|
106
|
+
readonly requiredObligations?: string[];
|
|
107
|
+
|
|
108
|
+
constructor(message: string, obligations?: string[], cause?: Error) {
|
|
109
|
+
super(message, cause);
|
|
110
|
+
if (obligations && obligations.length > 0) {
|
|
111
|
+
this.requiredObligations = obligations;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
/**
|
package/src/index.ts
CHANGED
|
@@ -4,5 +4,15 @@ export { attributeFQNsAsValues } from './policy/api.js';
|
|
|
4
4
|
export { version, clientType, tdfSpecVersion } from './version.js';
|
|
5
5
|
export { PlatformClient, type PlatformClientOptions, type PlatformServices } from './platform.js';
|
|
6
6
|
export * from './opentdf.js';
|
|
7
|
+
export {
|
|
8
|
+
TdfError,
|
|
9
|
+
PermissionDeniedError,
|
|
10
|
+
IntegrityError,
|
|
11
|
+
InvalidFileError,
|
|
12
|
+
DecryptError,
|
|
13
|
+
NetworkError,
|
|
14
|
+
AttributeValidationError,
|
|
15
|
+
ConfigurationError,
|
|
16
|
+
} from './errors.js';
|
|
7
17
|
export * from './seekable.js';
|
|
8
18
|
export * from '../tdf3/src/models/index.js';
|
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', // only one kao, no bulk
|
|
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,37 @@ export default class Client {
|
|
|
285
309
|
this.authProvider,
|
|
286
310
|
this.fulfillableObligationFQNs
|
|
287
311
|
);
|
|
312
|
+
// Upgrade any V1 responses to V2
|
|
313
|
+
upgradeRewrapResponseV1(rewrapResp);
|
|
314
|
+
|
|
315
|
+
const result = rewrapResp.responses?.[0]?.results?.[0];
|
|
316
|
+
if (!result) {
|
|
317
|
+
// This should not happen - KAS should always return at least one response and one result
|
|
318
|
+
// or the upgradeRewrapResponseV1 should have created them
|
|
319
|
+
throw new DecryptError('KAS rewrap response missing expected response or result');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const requiredObligations = getRequiredObligationFQNs(rewrapResp);
|
|
323
|
+
|
|
324
|
+
let entityWrappedKey: Uint8Array<ArrayBufferLike>;
|
|
325
|
+
switch (result.result.case) {
|
|
326
|
+
case 'kasWrappedKey': {
|
|
327
|
+
entityWrappedKey = result.result.value;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case 'error': {
|
|
331
|
+
handleRpcRewrapErrorString(
|
|
332
|
+
result.result.value,
|
|
333
|
+
getPlatformUrlFromKasEndpoint(kasRewrapUrl),
|
|
334
|
+
requiredObligations
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
default: {
|
|
338
|
+
throw new DecryptError('KAS rewrap response missing wrapped key');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
288
341
|
|
|
289
342
|
// Extract the iv and ciphertext
|
|
290
|
-
const entityWrappedKey = rewrapResp.entityWrappedKey;
|
|
291
343
|
const ivLength =
|
|
292
344
|
clientVersion == Client.SDK_INITIAL_RELEASE ? Client.INITIAL_RELEASE_IV_SIZE : Client.IV_SIZE;
|
|
293
345
|
const iv = entityWrappedKey.subarray(0, ivLength);
|
|
@@ -366,7 +418,7 @@ export default class Client {
|
|
|
366
418
|
}
|
|
367
419
|
|
|
368
420
|
return {
|
|
369
|
-
requiredObligations
|
|
421
|
+
requiredObligations,
|
|
370
422
|
unwrappedKey: unwrappedKey,
|
|
371
423
|
};
|
|
372
424
|
}
|
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,32 @@ 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
|
+
* Note: This mutates the response in place.
|
|
267
|
+
*/
|
|
268
|
+
export function upgradeRewrapResponseV1(response: RewrapResponse) {
|
|
269
|
+
if (response.responses.length > 0) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (response.entityWrappedKey.length === 0) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
response.responses = [
|
|
277
|
+
create(PolicyRewrapResultSchema, {
|
|
278
|
+
policyId: 'policy',
|
|
279
|
+
results: [
|
|
280
|
+
create(KeyAccessRewrapResultSchema, {
|
|
281
|
+
keyAccessObjectId: 'kao-0',
|
|
282
|
+
status: 'permit',
|
|
283
|
+
result: {
|
|
284
|
+
case: 'kasWrappedKey',
|
|
285
|
+
value: response.entityWrappedKey,
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
],
|
|
289
|
+
}),
|
|
290
|
+
];
|
|
291
|
+
}
|
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.
|
|
4
|
+
export const version = '0.6.0'; // x-release-please-version
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A string name used to label requests as coming from this library client.
|
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,67 @@ async function unwrapKey({
|
|
|
799
844
|
authProvider,
|
|
800
845
|
fulfillableObligations
|
|
801
846
|
);
|
|
802
|
-
|
|
847
|
+
// Upgrade V1 response to V2 format if needed
|
|
848
|
+
upgradeRewrapResponseV1(rewrapResp);
|
|
849
|
+
const { sessionPublicKey } = rewrapResp;
|
|
803
850
|
const requiredObligations = getRequiredObligationFQNs(rewrapResp);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
};
|
|
851
|
+
// Assume only one response and one result for now (V1 style)
|
|
852
|
+
const result = rewrapResp.responses?.[0]?.results?.[0];
|
|
853
|
+
if (!result) {
|
|
854
|
+
// This should not happen - KAS should always return at least one response and one result
|
|
855
|
+
// or the upgradeRewrapResponseV1 should have created them
|
|
856
|
+
throw new DecryptError('KAS rewrap response missing expected response or result');
|
|
823
857
|
}
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
858
|
+
const metadata = result.metadata;
|
|
859
|
+
// Handle the different cases of result.result
|
|
860
|
+
switch (result.result.case) {
|
|
861
|
+
case 'kasWrappedKey': {
|
|
862
|
+
const entityWrappedKey = result.result.value;
|
|
863
|
+
|
|
864
|
+
if (wrappingKeyAlgorithm === 'ec:secp256r1') {
|
|
865
|
+
const serverEphemeralKey: CryptoKey = await pemPublicToCrypto(sessionPublicKey);
|
|
866
|
+
const ekr = ephemeralEncryptionKeysRaw as CryptoKeyPair;
|
|
867
|
+
const kek = await keyAgreement(ekr.privateKey, serverEphemeralKey, {
|
|
868
|
+
hkdfSalt: await ztdfSalt,
|
|
869
|
+
hkdfHash: 'SHA-256',
|
|
870
|
+
});
|
|
871
|
+
const wrappedKeyAndNonce = entityWrappedKey;
|
|
872
|
+
const iv = wrappedKeyAndNonce.slice(0, 12);
|
|
873
|
+
const wrappedKey = wrappedKeyAndNonce.slice(12);
|
|
829
874
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
875
|
+
const dek = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, kek, wrappedKey);
|
|
876
|
+
|
|
877
|
+
return {
|
|
878
|
+
key: new Uint8Array(dek),
|
|
879
|
+
metadata,
|
|
880
|
+
requiredObligations,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
const key = Binary.fromArrayBuffer(entityWrappedKey);
|
|
884
|
+
const decryptedKeyBinary = await cryptoService.decryptWithPrivateKey(
|
|
885
|
+
key,
|
|
886
|
+
ephemeralEncryptionKeys.privateKey
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
return {
|
|
890
|
+
key: new Uint8Array(decryptedKeyBinary.asByteArray()),
|
|
891
|
+
metadata,
|
|
892
|
+
requiredObligations,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
case 'error': {
|
|
897
|
+
handleRpcRewrapErrorString(
|
|
898
|
+
result.result.value,
|
|
899
|
+
getPlatformUrlFromKasEndpoint(url),
|
|
900
|
+
requiredObligations
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
default: {
|
|
905
|
+
throw new DecryptError('KAS rewrap response missing wrapped key');
|
|
906
|
+
}
|
|
907
|
+
}
|
|
835
908
|
}
|
|
836
909
|
|
|
837
910
|
let poolSize = 1;
|