@virtru/dsp-sdk 0.2.4 → 0.3.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.
- package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_build.chunks.jsonl +3 -3
- package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_test.chunks.jsonl +36 -9
- package/.rush/temp/package-deps__phase_build.json +16 -8
- package/.rush/temp/shrinkwrap-deps.json +7 -1
- package/CHANGELOG.json +14 -0
- package/CHANGELOG.md +8 -1
- package/README.md +55 -0
- package/buf.gen.yaml +17 -0
- package/buf.yaml +4 -0
- package/dist/src/gen/virtru/common/common_pb.d.ts +151 -0
- package/dist/src/gen/virtru/common/common_pb.js +57 -0
- package/dist/src/gen/virtru/policy/certificates/v1/certificates_pb.d.ts +215 -0
- package/dist/src/gen/virtru/policy/certificates/v1/certificates_pb.js +50 -0
- package/dist/src/gen/virtru/policy/objects_pb.d.ts +188 -0
- package/dist/src/gen/virtru/policy/objects_pb.js +126 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/lib/client.d.ts +2 -0
- package/dist/src/lib/client.js +2 -0
- package/dist/src/lib/consts.d.ts +2 -0
- package/dist/src/lib/consts.js +7 -0
- package/dist/src/lib/utils.d.ts +136 -0
- package/dist/src/lib/utils.js +355 -2
- package/package.json +4 -2
- package/src/gen/virtru/common/common_pb.ts +179 -0
- package/src/gen/virtru/policy/certificates/v1/certificates_connect.ts +44 -0
- package/src/gen/virtru/policy/certificates/v1/certificates_pb.ts +260 -0
- package/src/gen/virtru/policy/objects_pb.ts +235 -0
- package/src/index.ts +15 -1
- package/src/lib/client.ts +3 -0
- package/src/lib/consts.ts +8 -0
- package/src/lib/utils.test.ts +376 -0
- package/src/lib/utils.ts +460 -4
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// @generated by protoc-gen-es v2.3.0 with parameter "target=ts"
|
|
2
|
+
// @generated from file virtru/policy/objects.proto (package virtru.policy, syntax proto3)
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
|
|
5
|
+
import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
|
|
6
|
+
import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
|
|
7
|
+
import { file_buf_validate_validate } from "../../buf/validate/validate_pb";
|
|
8
|
+
import type { Metadata } from "../common/common_pb";
|
|
9
|
+
import { file_virtru_common_common } from "../common/common_pb";
|
|
10
|
+
import type { Message } from "@bufbuild/protobuf";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Describes the file virtru/policy/objects.proto.
|
|
14
|
+
*/
|
|
15
|
+
export const file_virtru_policy_objects: GenFile = /*@__PURE__*/
|
|
16
|
+
fileDesc("Cht2aXJ0cnUvcG9saWN5L29iamVjdHMucHJvdG8SDXZpcnRydS5wb2xpY3kinwIKC0NlcnRpZmljYXRlEgoKAmlkGAEgASgJEg4KBmtleV9pZBgCIAEoCRIvCg1rZXlfYWxnb3JpdGhtGAMgASgOMhgudmlydHJ1LnBvbGljeS5BbGdvcml0aG0SLAoKa2V5X3N0YXR1cxgEIAEoDjIYLnZpcnRydS5wb2xpY3kuS2V5U3RhdHVzEhQKA3BlbRgFIAEoCUIHukgEcgIQARIVCgRuYW1lGAYgASgJQge6SARyAhABEigKCGtleV9tb2RlGAcgASgOMhYudmlydHJ1LnBvbGljeS5LZXlNb2RlEhMKC2ZpbmdlcnByaW50GAggASgJEikKCG1ldGFkYXRhGGQgASgLMhcudmlydHJ1LmNvbW1vbi5NZXRhZGF0YSKXAQoSQ2VydGlmaWNhdGVNYXBwaW5nEhQKAmlkGAEgASgJQgi6SAVyA7ABARIgCg5jZXJ0aWZpY2F0ZV9pZBgCIAEoCUIIukgFcgOwAQESHgoMbmFtZXNwYWNlX2lkGAMgASgJQgi6SAVyA7ABARIpCghtZXRhZGF0YRhkIAEoCzIXLnZpcnRydS5jb21tb24uTWV0YWRhdGEqmwEKCUFsZ29yaXRobRIZChVBTEdPUklUSE1fVU5TUEVDSUZJRUQQABIWChJBTEdPUklUSE1fUlNBXzIwNDgQARIWChJBTEdPUklUSE1fUlNBXzQwOTYQAhIVChFBTEdPUklUSE1fRUNfUDI1NhADEhUKEUFMR09SSVRITV9FQ19QMzg0EAQSFQoRQUxHT1JJVEhNX0VDX1A1MjEQBSpWCglLZXlTdGF0dXMSGgoWS0VZX1NUQVRVU19VTlNQRUNJRklFRBAAEhUKEUtFWV9TVEFUVVNfQUNUSVZFEAESFgoSS0VZX1NUQVRVU19ST1RBVEVEEAIqlAEKB0tleU1vZGUSGAoUS0VZX01PREVfVU5TUEVDSUZJRUQQABIcChhLRVlfTU9ERV9DT05GSUdfUk9PVF9LRVkQARIeChpLRVlfTU9ERV9QUk9WSURFUl9ST09UX0tFWRACEhMKD0tFWV9NT0RFX1JFTU9URRADEhwKGEtFWV9NT0RFX1BVQkxJQ19LRVlfT05MWRAEQnYKEWNvbS52aXJ0cnUucG9saWN5QgxPYmplY3RzUHJvdG9QAaICA1ZQWKoCDVZpcnRydS5Qb2xpY3nKAg1WaXJ0cnVcUG9saWN54gIZVmlydHJ1XFBvbGljeVxHUEJNZXRhZGF0YeoCDlZpcnRydTo6UG9saWN5YgZwcm90bzM", [file_buf_validate_validate, file_virtru_common_common]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @generated from message virtru.policy.Certificate
|
|
20
|
+
*/
|
|
21
|
+
export type Certificate = Message<"virtru.policy.Certificate"> & {
|
|
22
|
+
/**
|
|
23
|
+
* @generated from field: string id = 1;
|
|
24
|
+
*/
|
|
25
|
+
id: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @generated from field: string key_id = 2;
|
|
29
|
+
*/
|
|
30
|
+
keyId: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @generated from field: virtru.policy.Algorithm key_algorithm = 3;
|
|
34
|
+
*/
|
|
35
|
+
keyAlgorithm: Algorithm;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @generated from field: virtru.policy.KeyStatus key_status = 4;
|
|
39
|
+
*/
|
|
40
|
+
keyStatus: KeyStatus;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @generated from field: string pem = 5;
|
|
44
|
+
*/
|
|
45
|
+
pem: string;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The name of the certificate.
|
|
49
|
+
*
|
|
50
|
+
* @generated from field: string name = 6;
|
|
51
|
+
*/
|
|
52
|
+
name: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The key mode of the certificate.
|
|
56
|
+
*
|
|
57
|
+
* @generated from field: virtru.policy.KeyMode key_mode = 7;
|
|
58
|
+
*/
|
|
59
|
+
keyMode: KeyMode;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The fingerprint of the certificate.
|
|
63
|
+
*
|
|
64
|
+
* @generated from field: string fingerprint = 8;
|
|
65
|
+
*/
|
|
66
|
+
fingerprint: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @generated from field: virtru.common.Metadata metadata = 100;
|
|
70
|
+
*/
|
|
71
|
+
metadata?: Metadata;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Describes the message virtru.policy.Certificate.
|
|
76
|
+
* Use `create(CertificateSchema)` to create a new message.
|
|
77
|
+
*/
|
|
78
|
+
export const CertificateSchema: GenMessage<Certificate> = /*@__PURE__*/
|
|
79
|
+
messageDesc(file_virtru_policy_objects, 0);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @generated from message virtru.policy.CertificateMapping
|
|
83
|
+
*/
|
|
84
|
+
export type CertificateMapping = Message<"virtru.policy.CertificateMapping"> & {
|
|
85
|
+
/**
|
|
86
|
+
* @generated from field: string id = 1;
|
|
87
|
+
*/
|
|
88
|
+
id: string;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @generated from field: string certificate_id = 2;
|
|
92
|
+
*/
|
|
93
|
+
certificateId: string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @generated from field: string namespace_id = 3;
|
|
97
|
+
*/
|
|
98
|
+
namespaceId: string;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @generated from field: virtru.common.Metadata metadata = 100;
|
|
102
|
+
*/
|
|
103
|
+
metadata?: Metadata;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Describes the message virtru.policy.CertificateMapping.
|
|
108
|
+
* Use `create(CertificateMappingSchema)` to create a new message.
|
|
109
|
+
*/
|
|
110
|
+
export const CertificateMappingSchema: GenMessage<CertificateMapping> = /*@__PURE__*/
|
|
111
|
+
messageDesc(file_virtru_policy_objects, 1);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @generated from enum virtru.policy.Algorithm
|
|
115
|
+
*/
|
|
116
|
+
export enum Algorithm {
|
|
117
|
+
/**
|
|
118
|
+
* @generated from enum value: ALGORITHM_UNSPECIFIED = 0;
|
|
119
|
+
*/
|
|
120
|
+
UNSPECIFIED = 0,
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @generated from enum value: ALGORITHM_RSA_2048 = 1;
|
|
124
|
+
*/
|
|
125
|
+
RSA_2048 = 1,
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @generated from enum value: ALGORITHM_RSA_4096 = 2;
|
|
129
|
+
*/
|
|
130
|
+
RSA_4096 = 2,
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @generated from enum value: ALGORITHM_EC_P256 = 3;
|
|
134
|
+
*/
|
|
135
|
+
EC_P256 = 3,
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @generated from enum value: ALGORITHM_EC_P384 = 4;
|
|
139
|
+
*/
|
|
140
|
+
EC_P384 = 4,
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @generated from enum value: ALGORITHM_EC_P521 = 5;
|
|
144
|
+
*/
|
|
145
|
+
EC_P521 = 5,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Describes the enum virtru.policy.Algorithm.
|
|
150
|
+
*/
|
|
151
|
+
export const AlgorithmSchema: GenEnum<Algorithm> = /*@__PURE__*/
|
|
152
|
+
enumDesc(file_virtru_policy_objects, 0);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @generated from enum virtru.policy.KeyStatus
|
|
156
|
+
*/
|
|
157
|
+
export enum KeyStatus {
|
|
158
|
+
/**
|
|
159
|
+
* @generated from enum value: KEY_STATUS_UNSPECIFIED = 0;
|
|
160
|
+
*/
|
|
161
|
+
UNSPECIFIED = 0,
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @generated from enum value: KEY_STATUS_ACTIVE = 1;
|
|
165
|
+
*/
|
|
166
|
+
ACTIVE = 1,
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @generated from enum value: KEY_STATUS_ROTATED = 2;
|
|
170
|
+
*/
|
|
171
|
+
ROTATED = 2,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Describes the enum virtru.policy.KeyStatus.
|
|
176
|
+
*/
|
|
177
|
+
export const KeyStatusSchema: GenEnum<KeyStatus> = /*@__PURE__*/
|
|
178
|
+
enumDesc(file_virtru_policy_objects, 1);
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Describes the management and operational mode of a cryptographic key.
|
|
182
|
+
*
|
|
183
|
+
* @generated from enum virtru.policy.KeyMode
|
|
184
|
+
*/
|
|
185
|
+
export enum KeyMode {
|
|
186
|
+
/**
|
|
187
|
+
* KEY_MODE_UNSPECIFIED: Default, unspecified key mode. Indicates an uninitialized or error state.
|
|
188
|
+
*
|
|
189
|
+
* @generated from enum value: KEY_MODE_UNSPECIFIED = 0;
|
|
190
|
+
*/
|
|
191
|
+
UNSPECIFIED = 0,
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* KEY_MODE_CONFIG_ROOT_KEY: Local key management where the private key is wrapped by a Key Encryption Key (KEK)
|
|
195
|
+
* sourced from local configuration. Unwrapping and all cryptographic operations are performed locally.
|
|
196
|
+
*
|
|
197
|
+
* @generated from enum value: KEY_MODE_CONFIG_ROOT_KEY = 1;
|
|
198
|
+
*/
|
|
199
|
+
CONFIG_ROOT_KEY = 1,
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* KEY_MODE_PROVIDER_ROOT_KEY: Local key management where the private key is wrapped by a Key Encryption Key (KEK)
|
|
203
|
+
* managed by an external provider (e.g., a Hardware Security Module or Cloud KMS).
|
|
204
|
+
* Key unwrapping is delegated to the external provider; subsequent cryptographic operations
|
|
205
|
+
* are performed locally using the unwrapped key.
|
|
206
|
+
*
|
|
207
|
+
* @generated from enum value: KEY_MODE_PROVIDER_ROOT_KEY = 2;
|
|
208
|
+
*/
|
|
209
|
+
PROVIDER_ROOT_KEY = 2,
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* KEY_MODE_REMOTE: Remote key management where the private key is stored in, and all cryptographic
|
|
213
|
+
* operations are performed by, a remote Key Management Service (KMS) or HSM.
|
|
214
|
+
* The private key material never leaves the secure boundary of the remote system.
|
|
215
|
+
*
|
|
216
|
+
* @generated from enum value: KEY_MODE_REMOTE = 3;
|
|
217
|
+
*/
|
|
218
|
+
REMOTE = 3,
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* KEY_MODE_PUBLIC_KEY_ONLY: Public key only mode. Used when only a public key is available or required,
|
|
222
|
+
* typically for wrapping operations (e.g., encrypting a Data Encryption Key (DEK) for an external KAS).
|
|
223
|
+
* The corresponding private key is not managed or accessible by this system.
|
|
224
|
+
*
|
|
225
|
+
* @generated from enum value: KEY_MODE_PUBLIC_KEY_ONLY = 4;
|
|
226
|
+
*/
|
|
227
|
+
PUBLIC_KEY_ONLY = 4,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Describes the enum virtru.policy.KeyMode.
|
|
232
|
+
*/
|
|
233
|
+
export const KeyModeSchema: GenEnum<KeyMode> = /*@__PURE__*/
|
|
234
|
+
enumDesc(file_virtru_policy_objects, 2);
|
|
235
|
+
|
package/src/index.ts
CHANGED
|
@@ -22,7 +22,21 @@ export {
|
|
|
22
22
|
DSPClient,
|
|
23
23
|
} from './lib/client';
|
|
24
24
|
export { PlatformClient } from '@opentdf/sdk/platform';
|
|
25
|
-
export {
|
|
25
|
+
export {
|
|
26
|
+
createAuthInterceptor,
|
|
27
|
+
type Interceptor,
|
|
28
|
+
createAuthProvider,
|
|
29
|
+
createOpenTDFClient,
|
|
30
|
+
getObligations,
|
|
31
|
+
getUserEntitlements,
|
|
32
|
+
flattenObligations,
|
|
33
|
+
getTrustedCertificates,
|
|
34
|
+
validateJWSCertificateChain,
|
|
35
|
+
type JWSValidationResult,
|
|
36
|
+
type ObligationFeatureMap,
|
|
37
|
+
type SupportedObligations,
|
|
38
|
+
type GetUserEntitlementsResponse,
|
|
39
|
+
} from './lib/utils';
|
|
26
40
|
|
|
27
41
|
/**
|
|
28
42
|
* Exports the DSP wrapper of OpenTDF
|
package/src/lib/client.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { PolicyArtifactService } from '../gen/policyimportexport/v1/policy_impor
|
|
|
15
15
|
import { SharedService } from '../gen/shared/v1/shared_pb';
|
|
16
16
|
import { TaggingPDPService } from '../gen/tagging/pdp/v2/tagging_pb';
|
|
17
17
|
import { VersionService } from '../gen/version/v1/version_pb';
|
|
18
|
+
import { CertificateService } from '../gen/virtru/policy/certificates/v1/certificates_pb';
|
|
18
19
|
import { createAuthInterceptor } from './utils';
|
|
19
20
|
|
|
20
21
|
export interface DSPClientServicesV1 {
|
|
@@ -22,6 +23,7 @@ export interface DSPClientServicesV1 {
|
|
|
22
23
|
policyArtifactService: Client<typeof PolicyArtifactService>;
|
|
23
24
|
sharedService: Client<typeof SharedService>;
|
|
24
25
|
versionService: Client<typeof VersionService>;
|
|
26
|
+
certificateService: Client<typeof CertificateService>;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export interface DSPClientServicesV2 {
|
|
@@ -74,6 +76,7 @@ export class DSPClient {
|
|
|
74
76
|
policyArtifactService: createClient(PolicyArtifactService, transport),
|
|
75
77
|
sharedService: createClient(SharedService, transport),
|
|
76
78
|
versionService: createClient(VersionService, transport),
|
|
79
|
+
certificateService: createClient(CertificateService, transport),
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
this.v2 = {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Virtru Corporation. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Your use of this file is governed by the Virtu Terms of Service <https://www.virtru.com/terms-of-service/>.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const MAX_CERTS = 10; // reasonable upper bound for chain depth
|
|
8
|
+
export const MAX_CERT_BYTES = 64 * 1024; // 64KB per certificate
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Virtru Corporation. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Your use of this file is governed by the Virtu Terms of Service <https://www.virtru.com/terms-of-service/>.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
8
|
+
|
|
9
|
+
// Mock all @opentdf imports BEFORE importing utils
|
|
10
|
+
vi.mock('@opentdf/sdk', () => ({
|
|
11
|
+
AuthProviders: {
|
|
12
|
+
refreshAuthProvider: vi.fn()
|
|
13
|
+
},
|
|
14
|
+
OpenTDF: vi.fn(),
|
|
15
|
+
PermissionDeniedError: class PermissionDeniedError extends Error {}
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('@opentdf/sdk/platform', () => ({
|
|
19
|
+
PlatformClient: vi.fn()
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
vi.mock('@opentdf/sdk/platform/policy/objects_pb.js', () => ({
|
|
23
|
+
Certificate: class Certificate {
|
|
24
|
+
pem?: string;
|
|
25
|
+
constructor(data?: any) {
|
|
26
|
+
if (data) Object.assign(this, data);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('@opentdf/sdk/platform/common/common_pb.js', () => ({
|
|
32
|
+
ActiveStateEnum: {
|
|
33
|
+
ACTIVE: 1
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// Polyfill atob if needed
|
|
38
|
+
if (typeof (globalThis as any).atob === 'undefined') {
|
|
39
|
+
(globalThis as any).atob = (b64: string) => Buffer.from(b64, 'base64').toString('binary');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Mock pkijs/asn1js with a way to control them
|
|
43
|
+
const mockState = {
|
|
44
|
+
asn1Mode: 'ok' as 'ok' | 'bad-der',
|
|
45
|
+
verifyResult: { result: true } as { result: boolean; resultMessage?: string },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
vi.mock('asn1js', () => ({
|
|
49
|
+
fromBER: vi.fn((_: ArrayBuffer) => {
|
|
50
|
+
if (mockState.asn1Mode === 'bad-der') return { offset: -1 };
|
|
51
|
+
return { offset: 0, result: { subject: { typesAndValues: [] }, issuer: { typesAndValues: [] } } };
|
|
52
|
+
}),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock('pkijs', () => {
|
|
56
|
+
return {
|
|
57
|
+
Certificate: class Certificate {
|
|
58
|
+
public subject: any = { typesAndValues: [] };
|
|
59
|
+
public issuer: any = { typesAndValues: [] };
|
|
60
|
+
public subjectPublicKeyInfo: any = {};
|
|
61
|
+
constructor(_: any) {}
|
|
62
|
+
},
|
|
63
|
+
CertificateChainValidationEngine: class CertificateChainValidationEngine {
|
|
64
|
+
constructor(_: any) {}
|
|
65
|
+
async verify() {
|
|
66
|
+
return mockState.verifyResult;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Mock DSPClient
|
|
73
|
+
vi.mock('./client', () => ({
|
|
74
|
+
DSPClient: vi.fn()
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
// NOW import utils
|
|
78
|
+
import { validateJWSCertificateChain, getTrustedCertificates } from './utils';
|
|
79
|
+
import { DSPClient } from './client';
|
|
80
|
+
import { PlatformClient } from '@opentdf/sdk/platform';
|
|
81
|
+
import { AuthProviders } from '@opentdf/sdk';
|
|
82
|
+
|
|
83
|
+
describe('validateJWSCertificateChain', () => {
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
mockState.asn1Mode = 'ok';
|
|
86
|
+
mockState.verifyResult = { result: true };
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('throws error when x5c is empty', async () => {
|
|
90
|
+
await expect(validateJWSCertificateChain([], [{ pem: '---' }] as any))
|
|
91
|
+
.rejects.toThrow(/No certificates provided in x5c array/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('throws error when too many certificates in x5c', async () => {
|
|
95
|
+
const tooMany = new Array(11).fill('AQI=');
|
|
96
|
+
await expect(validateJWSCertificateChain(tooMany, [{ pem: '---' }] as any))
|
|
97
|
+
.rejects.toThrow(/Too many certificates in x5c/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('throws error when no trusted roots provided', async () => {
|
|
101
|
+
await expect(validateJWSCertificateChain(['AQI='], [] as any))
|
|
102
|
+
.rejects.toThrow(/No trusted root certificates provided/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('fails on invalid base64 in x5c', async () => {
|
|
106
|
+
await expect(validateJWSCertificateChain(['***not-base64***'], [{ pem: '---' }] as any))
|
|
107
|
+
.rejects.toThrow(/Failed to parse certificate at x5c\[0]/);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('fails on ASN.1 parse error', async () => {
|
|
111
|
+
mockState.asn1Mode = 'bad-der';
|
|
112
|
+
await expect(validateJWSCertificateChain(['AQI='], [{ pem: '---' }] as any))
|
|
113
|
+
.rejects.toThrow(/Invalid DER encoding/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns valid true on successful verification', async () => {
|
|
117
|
+
const pem = '-----BEGIN CERTIFICATE-----\nAQI=\n-----END CERTIFICATE-----';
|
|
118
|
+
mockState.verifyResult = { result: true };
|
|
119
|
+
const res = await validateJWSCertificateChain(['AQI='], [{ pem }] as any);
|
|
120
|
+
expect(res.valid).toBe(true);
|
|
121
|
+
expect(res.validationResult.warnings).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('returns warnings when some trusted roots fail to parse', async () => {
|
|
125
|
+
const pem = '-----BEGIN CERTIFICATE-----\nAQI=\n-----END CERTIFICATE-----';
|
|
126
|
+
const res = await validateJWSCertificateChain(['AQI='], [{ pem: '' }, { pem }] as any);
|
|
127
|
+
expect(res.valid).toBe(true);
|
|
128
|
+
expect(res.validationResult.warnings).toHaveLength(1);
|
|
129
|
+
expect(res.validationResult.warnings[0]).toMatch(/has no PEM data/);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('getTrustedCertificates', () => {
|
|
134
|
+
const mockOptions = {
|
|
135
|
+
oidc: {
|
|
136
|
+
clientId: 'test-client-id',
|
|
137
|
+
tokenEndpoint: 'https://auth.test.com/token',
|
|
138
|
+
userInfoEndpoint: 'https://auth.test.com/userinfo',
|
|
139
|
+
},
|
|
140
|
+
platformEndpoint: 'https://platform.test.com',
|
|
141
|
+
refreshToken: 'test-refresh-token',
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
let mockAuthProvider: any;
|
|
145
|
+
let mockPlatformClient: any;
|
|
146
|
+
let mockDSPClient: any;
|
|
147
|
+
let mockCertificateService: any;
|
|
148
|
+
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
vi.clearAllMocks();
|
|
151
|
+
|
|
152
|
+
// Mock crypto.subtle.generateKey for DPoP signing keys
|
|
153
|
+
if (!globalThis.crypto) {
|
|
154
|
+
(globalThis as any).crypto = {};
|
|
155
|
+
}
|
|
156
|
+
if (!globalThis.crypto.subtle) {
|
|
157
|
+
(globalThis.crypto as any).subtle = {};
|
|
158
|
+
}
|
|
159
|
+
(globalThis.crypto.subtle as any).generateKey = vi.fn().mockResolvedValue({
|
|
160
|
+
publicKey: {},
|
|
161
|
+
privateKey: {},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Mock auth provider
|
|
165
|
+
mockAuthProvider = {
|
|
166
|
+
updateClientPublicKey: vi.fn().mockResolvedValue(undefined),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
(AuthProviders.refreshAuthProvider as any).mockResolvedValue(mockAuthProvider);
|
|
170
|
+
|
|
171
|
+
// Mock certificate service
|
|
172
|
+
mockCertificateService = {
|
|
173
|
+
listCertificatesByNamespace: vi.fn(),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Mock DSP client
|
|
177
|
+
mockDSPClient = {
|
|
178
|
+
v1: {
|
|
179
|
+
certificateService: mockCertificateService,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
(DSPClient as any).mockImplementation(() => mockDSPClient);
|
|
184
|
+
|
|
185
|
+
// Mock platform client
|
|
186
|
+
mockPlatformClient = {
|
|
187
|
+
v1: {
|
|
188
|
+
namespace: {
|
|
189
|
+
listNamespaces: vi.fn(),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
(PlatformClient as any).mockImplementation(() => mockPlatformClient);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('successfully retrieves certificates from a single namespace', async () => {
|
|
198
|
+
const mockCertificate1 = { pem: 'cert1-pem-data' };
|
|
199
|
+
const mockCertificate2 = { pem: 'cert2-pem-data' };
|
|
200
|
+
|
|
201
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
202
|
+
namespaces: [{ id: 'namespace-1' }],
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
mockCertificateService.listCertificatesByNamespace.mockResolvedValue({
|
|
206
|
+
certificates: [mockCertificate1, mockCertificate2],
|
|
207
|
+
pagination: { nextOffset: 0 },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = await getTrustedCertificates(mockOptions);
|
|
211
|
+
|
|
212
|
+
expect(result).toHaveLength(2);
|
|
213
|
+
expect(result).toEqual([mockCertificate1, mockCertificate2]);
|
|
214
|
+
expect(mockCertificateService.listCertificatesByNamespace).toHaveBeenCalledWith({
|
|
215
|
+
namespaceId: 'namespace-1',
|
|
216
|
+
pagination: { limit: (100), offset: (0) },
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('successfully retrieves certificates from multiple namespaces', async () => {
|
|
221
|
+
const mockCert1 = { pem: 'cert1-pem' };
|
|
222
|
+
const mockCert2 = { pem: 'cert2-pem' };
|
|
223
|
+
const mockCert3 = { pem: 'cert3-pem' };
|
|
224
|
+
|
|
225
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
226
|
+
namespaces: [{ id: 'namespace-1' }, { id: 'namespace-2' }],
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
mockCertificateService.listCertificatesByNamespace
|
|
230
|
+
.mockResolvedValueOnce({
|
|
231
|
+
certificates: [mockCert1, mockCert2],
|
|
232
|
+
pagination: { nextOffset: (0) },
|
|
233
|
+
})
|
|
234
|
+
.mockResolvedValueOnce({
|
|
235
|
+
certificates: [mockCert3],
|
|
236
|
+
pagination: { nextOffset: (0) },
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const result = await getTrustedCertificates(mockOptions);
|
|
240
|
+
|
|
241
|
+
expect(result).toHaveLength(3);
|
|
242
|
+
expect(result).toEqual([mockCert1, mockCert2, mockCert3]);
|
|
243
|
+
expect(mockCertificateService.listCertificatesByNamespace).toHaveBeenCalledTimes(2);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('handles pagination correctly (multiple pages)', async () => {
|
|
247
|
+
const mockCert1 = { pem: 'cert1' };
|
|
248
|
+
const mockCert2 = { pem: 'cert2' };
|
|
249
|
+
const mockCert3 = { pem: 'cert3' };
|
|
250
|
+
|
|
251
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
252
|
+
namespaces: [{ id: 'namespace-1' }],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// First page
|
|
256
|
+
mockCertificateService.listCertificatesByNamespace
|
|
257
|
+
.mockResolvedValueOnce({
|
|
258
|
+
certificates: [mockCert1, mockCert2],
|
|
259
|
+
pagination: { nextOffset: (100) },
|
|
260
|
+
})
|
|
261
|
+
// Second page
|
|
262
|
+
.mockResolvedValueOnce({
|
|
263
|
+
certificates: [mockCert3],
|
|
264
|
+
pagination: { nextOffset: (0) },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const result = await getTrustedCertificates(mockOptions);
|
|
268
|
+
|
|
269
|
+
expect(result).toHaveLength(3);
|
|
270
|
+
expect(result).toEqual([mockCert1, mockCert2, mockCert3]);
|
|
271
|
+
expect(mockCertificateService.listCertificatesByNamespace).toHaveBeenCalledTimes(2);
|
|
272
|
+
expect(mockCertificateService.listCertificatesByNamespace).toHaveBeenNthCalledWith(1, {
|
|
273
|
+
namespaceId: 'namespace-1',
|
|
274
|
+
pagination: { limit: (100), offset: (0) },
|
|
275
|
+
});
|
|
276
|
+
expect(mockCertificateService.listCertificatesByNamespace).toHaveBeenNthCalledWith(2, {
|
|
277
|
+
namespaceId: 'namespace-1',
|
|
278
|
+
pagination: { limit: (100), offset: (100) },
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('handles empty certificate list', async () => {
|
|
283
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
284
|
+
namespaces: [{ id: 'namespace-1' }],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
mockCertificateService.listCertificatesByNamespace.mockResolvedValue({
|
|
288
|
+
certificates: [],
|
|
289
|
+
pagination: { nextOffset: (0) },
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const result = await getTrustedCertificates(mockOptions);
|
|
293
|
+
|
|
294
|
+
expect(result).toHaveLength(0);
|
|
295
|
+
expect(result).toEqual([]);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('handles errors from certificate service', async () => {
|
|
299
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
300
|
+
namespaces: [{ id: 'namespace-1' }],
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
mockCertificateService.listCertificatesByNamespace.mockRejectedValue(
|
|
304
|
+
new Error('Certificate service error')
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
await expect(getTrustedCertificates(mockOptions)).rejects.toThrow('Certificate service error');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('handles errors from namespace listing', async () => {
|
|
311
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockRejectedValue(
|
|
312
|
+
new Error('Namespace listing error')
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
await expect(getTrustedCertificates(mockOptions)).rejects.toThrow('Namespace listing error');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('initializes auth provider correctly', async () => {
|
|
319
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
320
|
+
namespaces: [],
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await getTrustedCertificates(mockOptions);
|
|
324
|
+
|
|
325
|
+
expect(AuthProviders.refreshAuthProvider).toHaveBeenCalled();
|
|
326
|
+
expect(mockAuthProvider.updateClientPublicKey).toHaveBeenCalled();
|
|
327
|
+
expect(globalThis.crypto.subtle.generateKey).toHaveBeenCalledWith(
|
|
328
|
+
{
|
|
329
|
+
name: 'ECDSA',
|
|
330
|
+
namedCurve: 'P-256',
|
|
331
|
+
},
|
|
332
|
+
true,
|
|
333
|
+
['sign', 'verify']
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('creates both PlatformClient and DSPClient with correct config', async () => {
|
|
338
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
339
|
+
namespaces: [],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await getTrustedCertificates(mockOptions);
|
|
343
|
+
|
|
344
|
+
expect(PlatformClient).toHaveBeenCalledWith({
|
|
345
|
+
authProvider: mockAuthProvider,
|
|
346
|
+
platformUrl: mockOptions.platformEndpoint,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(DSPClient).toHaveBeenCalledWith({
|
|
350
|
+
platformUrl: mockOptions.platformEndpoint,
|
|
351
|
+
authProvider: mockAuthProvider,
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('returns same Certificate type compatible with validateJWSCertificateChain', async () => {
|
|
356
|
+
const mockCert = { pem: '-----BEGIN CERTIFICATE-----\nAQI=\n-----END CERTIFICATE-----' };
|
|
357
|
+
|
|
358
|
+
mockPlatformClient.v1.namespace.listNamespaces.mockResolvedValue({
|
|
359
|
+
namespaces: [{ id: 'namespace-1' }],
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
mockCertificateService.listCertificatesByNamespace.mockResolvedValue({
|
|
363
|
+
certificates: [mockCert],
|
|
364
|
+
pagination: { nextOffset: (0) },
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const result = await getTrustedCertificates(mockOptions);
|
|
368
|
+
|
|
369
|
+
// Verify the result can be used with validateJWSCertificateChain
|
|
370
|
+
expect(result[0].pem).toBeDefined();
|
|
371
|
+
|
|
372
|
+
// Integration test: verify compatibility with validateJWSCertificateChain
|
|
373
|
+
const validationResult = await validateJWSCertificateChain(['AQI='], result);
|
|
374
|
+
expect(validationResult.valid).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
});
|