@virtru/dsp-sdk 0.5.0 → 0.7.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 (130) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +81 -3
  3. package/dist/src/fips.d.ts +7 -0
  4. package/dist/src/fips.js +12 -0
  5. package/dist/src/lib/dsp.d.ts +17 -1
  6. package/dist/src/lib/dsp.js +28 -5
  7. package/package.json +25 -4
  8. package/.prettierrc +0 -3
  9. package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_build.chunks.jsonl +0 -12
  10. package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_test.chunks.jsonl +0 -383
  11. package/.rush/temp/package-deps__phase_build.json +0 -76
  12. package/.rush/temp/shrinkwrap-deps.json +0 -1262
  13. package/CHANGELOG.json +0 -299
  14. package/buf.gen.yaml +0 -17
  15. package/buf.yaml +0 -4
  16. package/config/jest.config.json +0 -6
  17. package/config/rig.json +0 -8
  18. package/dist/tests/dsp-client.test.d.ts +0 -1
  19. package/dist/tests/dsp-client.test.js +0 -73
  20. package/dist/tests/dsp.test.d.ts +0 -1
  21. package/dist/tests/dsp.test.js +0 -92
  22. package/dist/tests/fips-crypto.unit.test.d.ts +0 -1
  23. package/dist/tests/fips-crypto.unit.test.js +0 -258
  24. package/dist/tests/mocks/create-export-artifacts.d.ts +0 -2
  25. package/dist/tests/mocks/create-export-artifacts.js +0 -13
  26. package/dist/tests/mocks/tagging-pdp-tag.d.ts +0 -2
  27. package/dist/tests/mocks/tagging-pdp-tag.js +0 -11
  28. package/dist/tests/mocks/well-known-configuration.d.ts +0 -2
  29. package/dist/tests/mocks/well-known-configuration.js +0 -12
  30. package/dist/tests/setup-msw.d.ts +0 -9
  31. package/dist/tests/setup-msw.js +0 -30
  32. package/dist/tests/setup-unit.d.ts +0 -7
  33. package/dist/tests/setup-unit.js +0 -70
  34. package/eslint.config.mjs +0 -28
  35. package/lib-commonjs/src/gen/buf/validate/validate_pb.js +0 -487
  36. package/lib-commonjs/src/gen/config/v1/config_pb.js +0 -142
  37. package/lib-commonjs/src/gen/config/v1/kas_config_pb.js +0 -72
  38. package/lib-commonjs/src/gen/config/v1/meta_pb.js +0 -36
  39. package/lib-commonjs/src/gen/config/v1/outlook_config_pb.js +0 -67
  40. package/lib-commonjs/src/gen/config/v1/secureviewer_config_pb.js +0 -67
  41. package/lib-commonjs/src/gen/config/v1/tagging_pb.js +0 -246
  42. package/lib-commonjs/src/gen/google/api/annotations_pb.js +0 -33
  43. package/lib-commonjs/src/gen/google/api/http_pb.js +0 -44
  44. package/lib-commonjs/src/gen/policyimportexport/v1/policy_import_export_pb.js +0 -93
  45. package/lib-commonjs/src/gen/shared/v1/shared_pb.js +0 -95
  46. package/lib-commonjs/src/gen/tagging/pdp/v2/tagging_pb.js +0 -277
  47. package/lib-commonjs/src/gen/version/v1/version_pb.js +0 -40
  48. package/lib-commonjs/src/gen/virtru/common/common_pb.js +0 -76
  49. package/lib-commonjs/src/gen/virtru/policy/certificates/v1/certificates_pb.js +0 -77
  50. package/lib-commonjs/src/gen/virtru/policy/objects_pb.js +0 -143
  51. package/lib-commonjs/src/index.js +0 -48
  52. package/lib-commonjs/src/lib/client.js +0 -66
  53. package/lib-commonjs/src/lib/consts.js +0 -10
  54. package/lib-commonjs/src/lib/dsp.js +0 -49
  55. package/lib-commonjs/src/lib/fips-crypto/asymmetric.js +0 -126
  56. package/lib-commonjs/src/lib/fips-crypto/key-format.js +0 -243
  57. package/lib-commonjs/src/lib/fips-crypto/keys.js +0 -134
  58. package/lib-commonjs/src/lib/fips-crypto/primitives.js +0 -112
  59. package/lib-commonjs/src/lib/fips-crypto/symmetric.js +0 -162
  60. package/lib-commonjs/src/lib/fips-crypto-service.js +0 -57
  61. package/lib-commonjs/src/lib/utils.js +0 -401
  62. package/lib-commonjs/tests/dsp-client.test.js +0 -75
  63. package/lib-commonjs/tests/dsp.test.js +0 -94
  64. package/lib-commonjs/tests/fips-crypto.unit.test.js +0 -260
  65. package/lib-commonjs/tests/mocks/create-export-artifacts.js +0 -17
  66. package/lib-commonjs/tests/mocks/tagging-pdp-tag.js +0 -15
  67. package/lib-commonjs/tests/mocks/well-known-configuration.js +0 -16
  68. package/lib-commonjs/tests/setup-msw.js +0 -33
  69. package/lib-commonjs/tests/setup-unit.js +0 -72
  70. package/public/virtru.wasm +0 -0
  71. package/src/gen/buf/validate/validate_pb.ts +0 -4879
  72. package/src/gen/config/formatters/testdata/v1/test_pb.ts +0 -287
  73. package/src/gen/config/v1/config_connect.ts +0 -111
  74. package/src/gen/config/v1/config_pb.ts +0 -439
  75. package/src/gen/config/v1/kas_config_pb.ts +0 -183
  76. package/src/gen/config/v1/meta_pb.ts +0 -72
  77. package/src/gen/config/v1/outlook_config_pb.ts +0 -242
  78. package/src/gen/config/v1/secureviewer_config_pb.ts +0 -161
  79. package/src/gen/config/v1/tagging_pb.ts +0 -456
  80. package/src/gen/google/api/annotations_pb.ts +0 -43
  81. package/src/gen/google/api/http_pb.ts +0 -486
  82. package/src/gen/helloworld/v1/helloworld_connect.ts +0 -49
  83. package/src/gen/helloworld/v1/helloworld_pb.ts +0 -104
  84. package/src/gen/kas/nanotdf/v1/nanotdf_rewrap_connect.ts +0 -39
  85. package/src/gen/kas/nanotdf/v1/nanotdf_rewrap_pb.ts +0 -207
  86. package/src/gen/policyimportexport/v1/policy_import_export_connect.ts +0 -56
  87. package/src/gen/policyimportexport/v1/policy_import_export_pb.ts +0 -332
  88. package/src/gen/shared/v1/shared_connect.ts +0 -61
  89. package/src/gen/shared/v1/shared_pb.ts +0 -240
  90. package/src/gen/shared/v2/shared_connect.ts +0 -39
  91. package/src/gen/shared/v2/shared_pb.ts +0 -122
  92. package/src/gen/tagging/pdp/v2/externalprocessors/processor_connect.ts +0 -156
  93. package/src/gen/tagging/pdp/v2/externalprocessors/processor_pb.ts +0 -709
  94. package/src/gen/tagging/pdp/v2/stanag/stanag_pb.ts +0 -953
  95. package/src/gen/tagging/pdp/v2/tagging_connect.ts +0 -73
  96. package/src/gen/tagging/pdp/v2/tagging_pb.ts +0 -1064
  97. package/src/gen/version/v1/version_connect.ts +0 -31
  98. package/src/gen/version/v1/version_pb.ts +0 -143
  99. package/src/gen/virtru/common/common_pb.ts +0 -201
  100. package/src/gen/virtru/policy/certificates/v1/certificates_connect.ts +0 -44
  101. package/src/gen/virtru/policy/certificates/v1/certificates_pb.ts +0 -305
  102. package/src/gen/virtru/policy/objects_pb.ts +0 -253
  103. package/src/gen/web-admin/v1/config_connect.ts +0 -52
  104. package/src/gen/web-admin/v1/config_pb.ts +0 -173
  105. package/src/index.ts +0 -62
  106. package/src/lib/client.ts +0 -90
  107. package/src/lib/consts.ts +0 -8
  108. package/src/lib/dsp.ts +0 -65
  109. package/src/lib/fips-crypto/asymmetric.ts +0 -162
  110. package/src/lib/fips-crypto/key-format.ts +0 -287
  111. package/src/lib/fips-crypto/keys.ts +0 -190
  112. package/src/lib/fips-crypto/primitives.ts +0 -197
  113. package/src/lib/fips-crypto/symmetric.ts +0 -213
  114. package/src/lib/fips-crypto-service.ts +0 -58
  115. package/src/lib/utils.test.ts +0 -415
  116. package/src/lib/utils.ts +0 -525
  117. package/src/vite-env.d.ts +0 -7
  118. package/temp/build/lint/_eslint-5eVG3S6w.json +0 -38
  119. package/temp/test/jest/haste-map-b055a9550e6d9f9b43d8ad29b1607278-49d19aee56a0732eca9c014a51272338-8710993d07b75a801b0956e67ffc46fa +0 -0
  120. package/tests/dsp-client.test.ts +0 -95
  121. package/tests/dsp.test.ts +0 -119
  122. package/tests/fips-crypto.unit.test.ts +0 -412
  123. package/tests/mocks/create-export-artifacts.ts +0 -16
  124. package/tests/mocks/tagging-pdp-tag.ts +0 -13
  125. package/tests/mocks/well-known-configuration.ts +0 -15
  126. package/tests/setup-msw.ts +0 -35
  127. package/tests/setup-unit.ts +0 -108
  128. package/tsconfig.json +0 -32
  129. package/update-protos.sh +0 -63
  130. package/vitest.config.ts +0 -48
@@ -1,287 +0,0 @@
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 {
8
- type KeyAlgorithm,
9
- type KeyOptions,
10
- type PrivateKey,
11
- type PublicKey,
12
- type PublicKeyInfo,
13
- WebCryptoService,
14
- } from '@opentdf/sdk/singlecontainer';
15
- import { ConfigurationError } from '@opentdf/sdk';
16
- import { formatAsPem, removePemFormatting, guessAlgorithmName } from '@opentdf/sdk/cryptoutils';
17
- import { base64, hex } from '@opentdf/sdk/encodings';
18
- import { getVirtruCrypto, type VirtruKeyUsage } from './primitives';
19
- import { wrapPublicKey, wrapPrivateKey, unwrapPublicKey, unwrapPrivateKey } from './keys';
20
-
21
- const VERIFY_USAGE: readonly VirtruKeyUsage[] = ['verify'];
22
- const ENCRYPT_VERIFY_USAGES: readonly VirtruKeyUsage[] = ['encrypt', 'verify'];
23
- const SIGN_USAGE: readonly VirtruKeyUsage[] = ['sign'];
24
- const DECRYPT_USAGE: readonly VirtruKeyUsage[] = ['decrypt'];
25
-
26
- // ─── PEM Utilities ────────────────────────────────────────────────────────────
27
-
28
- /**
29
- * Strip PEM headers/footers and whitespace, decode base64 → raw DER bytes.
30
- */
31
- function pemToBytes(pem: string): Uint8Array {
32
- return new Uint8Array(base64.decodeArrayBuffer(removePemFormatting(pem)));
33
- }
34
-
35
- /**
36
- * Parse the RSA modulus bit length from SPKI-formatted DER bytes.
37
- * Walks: SEQUENCE { AlgorithmIdentifier, BIT STRING { RSAPublicKey { modulus INTEGER } } }
38
- */
39
- function rsaModulusBitsFromSpki(spki: Uint8Array): number {
40
- let pos = 0;
41
-
42
- const readLen = (): number => {
43
- let len = spki[pos++];
44
- if (len & 0x80) {
45
- const n = len & 0x7f;
46
- len = 0;
47
- for (let i = 0; i < n; i++) len = (len << 8) | spki[pos++];
48
- }
49
- return len;
50
- };
51
-
52
- // SPKI outer SEQUENCE
53
- pos++; readLen();
54
- // Skip AlgorithmIdentifier SEQUENCE
55
- pos++; pos += readLen();
56
- // BIT STRING
57
- pos++; readLen();
58
- pos++; // unused-bits byte
59
- // RSAPublicKey SEQUENCE
60
- pos++; readLen();
61
- // modulus INTEGER tag + length
62
- pos++;
63
- const modulusLen = readLen();
64
- // Leading 0x00 is a sign byte for positive INTEGERs — exclude it
65
- const padding = spki[pos] === 0x00 ? 1 : 0;
66
- return (modulusLen - padding) * 8;
67
- }
68
-
69
- // ─── ASN.1 Helpers ────────────────────────────────────────────────────────────
70
-
71
- function asn1EncodeLength(len: number): Uint8Array {
72
- if (len < 0x80) return new Uint8Array([len]);
73
- if (len < 0x100) return new Uint8Array([0x81, len]);
74
- return new Uint8Array([0x82, (len >> 8) & 0xff, len & 0xff]);
75
- }
76
-
77
- function asn1Concat(arrays: Uint8Array[]): Uint8Array {
78
- const total = arrays.reduce((s, a) => s + a.length, 0);
79
- const out = new Uint8Array(total);
80
- let off = 0;
81
- for (const a of arrays) { out.set(a, off); off += a.length; }
82
- return out;
83
- }
84
-
85
- function asn1Sequence(elements: Uint8Array[]): Uint8Array {
86
- const body = asn1Concat(elements);
87
- return asn1Concat([new Uint8Array([0x30]), asn1EncodeLength(body.length), body]);
88
- }
89
-
90
- function asn1BitString(data: Uint8Array): Uint8Array {
91
- const inner = asn1Concat([new Uint8Array([0x00]), data]); // 0x00 = zero unused bits
92
- return asn1Concat([new Uint8Array([0x03]), asn1EncodeLength(inner.length), inner]);
93
- }
94
-
95
- // RSA AlgorithmIdentifier: SEQUENCE { OID 1.2.840.113549.1.1.1, NULL }
96
- const RSA_ALG_ID = asn1Sequence([
97
- new Uint8Array([0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]),
98
- new Uint8Array([0x05, 0x00]),
99
- ]);
100
-
101
- /**
102
- * Returns true if the DER bytes are already SPKI format (have an AlgorithmIdentifier OID).
103
- */
104
- function isSpkiFormat(derBytes: Uint8Array): boolean {
105
- const hexStr = hex.encodeArrayBuffer(derBytes.buffer as ArrayBuffer);
106
- try {
107
- guessAlgorithmName(hexStr);
108
- return true;
109
- } catch {
110
- return false;
111
- }
112
- }
113
-
114
- /**
115
- * Wraps a bare RSAPublicKey (PKCS#1) blob in an SPKI AlgorithmIdentifier container.
116
- * VirtruCrypto returns PKCS#1 for generated keys; imported keys are already SPKI.
117
- */
118
- function normalizePublicKeyToSpki(exported: Uint8Array): Uint8Array {
119
- if (isSpkiFormat(exported)) return exported;
120
- return asn1Sequence([RSA_ALG_ID, asn1BitString(exported)]);
121
- }
122
-
123
- /**
124
- * Wraps a bare RSAPrivateKey (PKCS#1) blob in a PKCS#8 PrivateKeyInfo container.
125
- * VirtruCrypto's exportKey('pkcs8') returns raw PKCS#1 DER — the 'pkcs8' flag
126
- * just selects "export private key" vs "export public key" from the combined slot.
127
- * We wrap it here to produce standard PKCS#8:
128
- * SEQUENCE { INTEGER 0, AlgorithmIdentifier, OCTET STRING { RSAPrivateKey } }
129
- */
130
- function normalizePrivateKeyToPkcs8(pkcs1: Uint8Array): Uint8Array {
131
- const version = new Uint8Array([0x02, 0x01, 0x00]); // INTEGER 0
132
- const privateKeyOctetString = asn1Concat([
133
- new Uint8Array([0x04]),
134
- asn1EncodeLength(pkcs1.length),
135
- pkcs1,
136
- ]);
137
- return asn1Sequence([version, RSA_ALG_ID, privateKeyOctetString]);
138
- }
139
-
140
- // ─── Key Import ───────────────────────────────────────────────────────────────
141
-
142
- /**
143
- * Import a PEM public key (SPKI) as an opaque PublicKey.
144
- * Only RSA keys are supported in FIPS mode.
145
- *
146
- * @param pem - PEM-encoded public key (`-----BEGIN PUBLIC KEY-----`)
147
- * @param options.usage - `'encrypt'` (RSA-OAEP) or `'sign'` (RS256 verify). Defaults to `'encrypt'`.
148
- * @param options.extractable - Defaults to `true`.
149
- * @param options.algorithmHint - Skips SPKI parsing when provided.
150
- */
151
- export async function importPublicKey(pem: string, options: KeyOptions): Promise<PublicKey> {
152
- const { usage = 'encrypt', extractable = true, algorithmHint } = options;
153
-
154
- const keyBytes = pemToBytes(pem);
155
-
156
- let algorithm: KeyAlgorithm;
157
- if (algorithmHint) {
158
- algorithm = algorithmHint;
159
- } else {
160
- const modulusBits = rsaModulusBitsFromSpki(keyBytes);
161
- algorithm = modulusBits <= 2048 ? 'rsa:2048' : 'rsa:4096';
162
- }
163
-
164
- if (!algorithm.startsWith('rsa:')) {
165
- throw new ConfigurationError(
166
- `Key algorithm '${algorithm}' not supported in FIPS mode. Only RSA keys are supported.`
167
- );
168
- }
169
-
170
- // VirtruCrypto uses RSASSA-PKCS1-v1_5 as the import algorithm name for all RSA keys;
171
- // the per-operation algorithm (RSA-OAEP vs PKCS1v15) is specified at call time.
172
- const usages = usage === 'sign' ? VERIFY_USAGE : ENCRYPT_VERIFY_USAGES;
173
-
174
- const crypto = await getVirtruCrypto();
175
- const vKey = await crypto.importKey(
176
- 'spki',
177
- keyBytes,
178
- { name: 'RSASSA-PKCS1-v1_5' },
179
- extractable,
180
- usages
181
- );
182
-
183
- return wrapPublicKey(vKey, algorithm);
184
- }
185
-
186
- /**
187
- * Import a PEM private key (PKCS#8) as an opaque PrivateKey.
188
- * Only RSA keys are supported in FIPS mode.
189
- *
190
- * @param pem - PEM-encoded private key (`-----BEGIN PRIVATE KEY-----`)
191
- * @param options.usage - `'encrypt'` (RSA-OAEP decrypt) or `'sign'` (RS256). Defaults to `'encrypt'`.
192
- * @param options.extractable - Defaults to `false`.
193
- * @param options.algorithmHint - Algorithm label to attach. Defaults to `'rsa:2048'`.
194
- */
195
- export async function importPrivateKey(pem: string, options: KeyOptions): Promise<PrivateKey> {
196
- const { usage = 'encrypt', extractable = false, algorithmHint } = options;
197
-
198
- const keyBytes = pemToBytes(pem);
199
- const algorithm: KeyAlgorithm = algorithmHint ?? 'rsa:2048';
200
-
201
- if (!algorithm.startsWith('rsa:')) {
202
- throw new ConfigurationError(
203
- `Key algorithm '${algorithm}' not supported in FIPS mode. Only RSA keys are supported.`
204
- );
205
- }
206
-
207
- const usages = usage === 'sign' ? SIGN_USAGE : DECRYPT_USAGE;
208
-
209
- const crypto = await getVirtruCrypto();
210
- const vKey = await crypto.importKey(
211
- 'pkcs8',
212
- keyBytes,
213
- { name: 'RSASSA-PKCS1-v1_5' },
214
- extractable,
215
- usages
216
- );
217
-
218
- return wrapPrivateKey(vKey, algorithm);
219
- }
220
-
221
- // ─── Key Export ───────────────────────────────────────────────────────────────
222
-
223
- /**
224
- * Export an opaque public key to PEM (SPKI / `-----BEGIN PUBLIC KEY-----`).
225
- * Handles VirtruCrypto's PKCS#1 output for generated keys transparently.
226
- */
227
- export async function exportPublicKeyPem(key: PublicKey): Promise<string> {
228
- const crypto = await getVirtruCrypto();
229
- const exported = await crypto.exportKey('spki', unwrapPublicKey(key));
230
- const spki = normalizePublicKeyToSpki(exported);
231
- return formatAsPem(spki.buffer as ArrayBuffer, 'PUBLIC KEY');
232
- }
233
-
234
- /**
235
- * Export an opaque public key to JWK.
236
- * Uses WebCrypto for the JWK conversion after normalizing to SPKI.
237
- */
238
- export async function exportPublicKeyJwk(key: PublicKey): Promise<JsonWebKey> {
239
- const crypto = await getVirtruCrypto();
240
- const exported = await crypto.exportKey('spki', unwrapPublicKey(key));
241
- const spki = normalizePublicKeyToSpki(exported);
242
- const webKey = await globalThis.crypto.subtle.importKey(
243
- 'spki',
244
- spki,
245
- { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
246
- true,
247
- ['verify']
248
- );
249
- return globalThis.crypto.subtle.exportKey('jwk', webKey);
250
- }
251
-
252
- /**
253
- * Export an opaque private key to PEM (PKCS#8 / `-----BEGIN PRIVATE KEY-----`).
254
- *
255
- * FOR TESTING / DEVELOPMENT ONLY — not included in the CryptoService interface.
256
- * Only works for keys that were imported via importPrivateKey (PKCS#8 format).
257
- * Generated private keys cannot be exported from the FIPS keystore.
258
- */
259
- export async function exportPrivateKeyPem(key: PrivateKey): Promise<string> {
260
- const crypto = await getVirtruCrypto();
261
- const exported = await crypto.exportKey('pkcs8', unwrapPrivateKey(key));
262
- const pkcs8 = normalizePrivateKeyToPkcs8(exported);
263
- return formatAsPem(pkcs8.buffer as ArrayBuffer, 'PRIVATE KEY');
264
- }
265
-
266
- // ─── Public Key Utils ─────────────────────────────────────
267
-
268
- /**
269
- * Use the WebCrypto API to parse a PEM public key and extract its algorithm info.
270
- */
271
- export async function parsePublicKeyPem (pem: string): Promise<PublicKeyInfo> {
272
- return WebCryptoService.parsePublicKeyPem(pem);
273
- }
274
-
275
- /**
276
- * Use the WebCrypto API to extract the public key PEM from a certificate or PEM string.
277
- */
278
- export async function extractPublicKeyPem(certOrPem: string): Promise<string> {
279
- return WebCryptoService.extractPublicKeyPem(certOrPem);
280
- }
281
-
282
- /**
283
- * Use the WebCrypto API to convert a JWK public key to PEM format.
284
- */
285
- export async function jwkToPublicKeyPem(jwk: JsonWebKey): Promise<string> {
286
- return WebCryptoService.jwkToPublicKeyPem(jwk);
287
- }
@@ -1,190 +0,0 @@
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 {
8
- type KeyAlgorithm,
9
- type KeyPair,
10
- type PrivateKey,
11
- type PublicKey,
12
- type SymmetricKey,
13
- type ECCurve,
14
- } from '@opentdf/sdk/singlecontainer';
15
-
16
- interface VirtruCryptoKeyHandle {
17
- release(): void;
18
- [key: string]: unknown;
19
- }
20
-
21
- // Opaque Key Types
22
- //
23
- // Asymmetric keys (PublicKey, PrivateKey) store a VirtruCryptoKey handle,
24
- // keeping the key material inside the FIPS WASM keystore at all times.
25
- //
26
- // Symmetric keys (SymmetricKey) store a VirtruCryptoKey handle pointing to
27
- // either a keystore slot (imported keys) or a heap allocation (generated keys).
28
- //
29
- // All opaque wrappers are registered with keyFinalizer so WASM slots/heap
30
- // memory are freed automatically when the JS wrapper is garbage collected.
31
- // This covers the common case where callers (e.g. the opentdf SDK) never
32
- // call release() explicitly — without this, every TDF operation would
33
- // permanently consume a keystore slot and the WASM would exhaust all 50 slots (NUM_KEYS).
34
-
35
- export type OpaquePublicKey = PublicKey & { readonly _internal: VirtruCryptoKeyHandle };
36
- export type OpaquePrivateKey = PrivateKey & { readonly _internal: VirtruCryptoKeyHandle };
37
- export type OpaqueSymmetricKey = SymmetricKey & { readonly _internal: VirtruCryptoKeyHandle };
38
-
39
- type KeyBrand = 'PublicKey' | 'PrivateKey' | 'SymmetricKey';
40
-
41
- interface OpaqueKeyShapeBase<TBrand extends KeyBrand> {
42
- _brand: TBrand;
43
- _internal: VirtruCryptoKeyHandle;
44
- }
45
-
46
- interface OpaquePublicKeyShape extends OpaqueKeyShapeBase<'PublicKey'> {
47
- algorithm: KeyAlgorithm;
48
- modulusBits?: number;
49
- curve?: ECCurve;
50
- }
51
-
52
- interface OpaquePrivateKeyShape extends OpaqueKeyShapeBase<'PrivateKey'> {
53
- algorithm: KeyAlgorithm;
54
- modulusBits?: number;
55
- }
56
-
57
- interface OpaqueSymmetricKeyShape extends OpaqueKeyShapeBase<'SymmetricKey'> {
58
- length: number;
59
- }
60
-
61
- // FinalizationRegistry: when an opaque key wrapper is GC'd, release its WASM slot.
62
- // The held value is any object with a release() method — either a VirtruCryptoKey
63
- // directly, or a SharedReleasable for key pairs that share a single WASM handle.
64
- // The registry keeps the held value alive until the callback fires, so release()
65
- // is always safe to call inside the callback.
66
- export const keyFinalizer = new FinalizationRegistry((handle: { release(): void }) => {
67
- handle.release();
68
- });
69
-
70
- // SharedReleasable wraps a single VirtruCryptoKey handle that is referenced by
71
- // two wrappers (e.g. the public and private halves of a generated key pair).
72
- // release() decrements a ref count and only frees the underlying WASM slot
73
- // when both wrappers have been garbage collected, preventing a double-free.
74
- function sharedReleasable(key: VirtruCryptoKeyHandle): { release(): void } {
75
- let refs = 2;
76
- return {
77
- release() {
78
- if (--refs === 0) key.release();
79
- },
80
- };
81
- }
82
-
83
- // Wrap Helpers
84
-
85
- export function wrapPublicKey(key: VirtruCryptoKeyHandle, algorithm: KeyAlgorithm): PublicKey {
86
- const result: OpaquePublicKeyShape = {
87
- _brand: 'PublicKey' as const,
88
- algorithm,
89
- _internal: key,
90
- };
91
- if (algorithm.startsWith('rsa:')) {
92
- result.modulusBits = parseInt(algorithm.split(':')[1], 10);
93
- } else if (algorithm.startsWith('ec:')) {
94
- const curveMap: Record<string, ECCurve> = {
95
- secp256r1: 'P-256',
96
- secp384r1: 'P-384',
97
- secp521r1: 'P-521',
98
- };
99
- result.curve = curveMap[algorithm.split(':')[1]];
100
- }
101
- keyFinalizer.register(result, key);
102
- return result as PublicKey;
103
- }
104
-
105
- export function wrapPrivateKey(key: VirtruCryptoKeyHandle, algorithm: KeyAlgorithm): PrivateKey {
106
- const result: OpaquePrivateKeyShape = {
107
- _brand: 'PrivateKey' as const,
108
- algorithm,
109
- _internal: key,
110
- };
111
- if (algorithm.startsWith('rsa:')) {
112
- result.modulusBits = parseInt(algorithm.split(':')[1], 10);
113
- }
114
- keyFinalizer.register(result, key);
115
- return result as PrivateKey;
116
- }
117
-
118
- export function wrapSymmetricKey(key: VirtruCryptoKeyHandle, lengthBits: number): SymmetricKey {
119
- const result: OpaqueSymmetricKeyShape = {
120
- _brand: 'SymmetricKey' as const,
121
- length: lengthBits,
122
- _internal: key,
123
- };
124
- keyFinalizer.register(result, key);
125
- return result as SymmetricKey;
126
- }
127
-
128
- export function wrapKeyPair(
129
- publicKey: VirtruCryptoKeyHandle,
130
- privateKey: VirtruCryptoKeyHandle,
131
- algorithm: KeyAlgorithm
132
- ): KeyPair {
133
- if (publicKey === privateKey) {
134
- // VirtruCrypto returns a single combined handle for generated key pairs.
135
- // Use a ref-counted releasable so the WASM slot is freed exactly once
136
- // after both wrappers are garbage collected.
137
- const shared = sharedReleasable(publicKey);
138
- const pub: OpaquePublicKeyShape = {
139
- _brand: 'PublicKey' as const,
140
- algorithm,
141
- _internal: publicKey,
142
- };
143
- const priv: OpaquePrivateKeyShape = {
144
- _brand: 'PrivateKey' as const,
145
- algorithm,
146
- _internal: privateKey,
147
- };
148
- if (algorithm.startsWith('rsa:')) {
149
- pub.modulusBits = parseInt(algorithm.split(':')[1], 10);
150
- priv.modulusBits = pub.modulusBits;
151
- } else if (algorithm.startsWith('ec:')) {
152
- const curveMap: Record<string, ECCurve> = {
153
- secp256r1: 'P-256',
154
- secp384r1: 'P-384',
155
- secp521r1: 'P-521',
156
- };
157
- pub.curve = curveMap[algorithm.split(':')[1]];
158
- }
159
- keyFinalizer.register(pub, shared);
160
- keyFinalizer.register(priv, shared);
161
- return { publicKey: pub as PublicKey, privateKey: priv as PrivateKey };
162
- }
163
- return {
164
- publicKey: wrapPublicKey(publicKey, algorithm),
165
- privateKey: wrapPrivateKey(privateKey, algorithm),
166
- };
167
- }
168
-
169
- // ─── Unwrap Helpers ───────────────────────────────────────────────────────────
170
-
171
- export function unwrapPublicKey(key: PublicKey): VirtruCryptoKeyHandle {
172
- return (key as OpaquePublicKey)._internal;
173
- }
174
-
175
- export function unwrapPrivateKey(key: PrivateKey): VirtruCryptoKeyHandle {
176
- return (key as OpaquePrivateKey)._internal;
177
- }
178
-
179
- export function unwrapSymmetricKey(key: SymmetricKey): VirtruCryptoKeyHandle {
180
- return (key as OpaqueSymmetricKey)._internal;
181
- }
182
-
183
- export function isSymmetricKey(value: unknown): value is SymmetricKey {
184
- if (!value || typeof value !== 'object' || !('_brand' in value)) {
185
- return false;
186
- }
187
-
188
- const candidate = value as { _brand?: unknown };
189
- return candidate._brand === 'SymmetricKey';
190
- }
@@ -1,197 +0,0 @@
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 { ConfigurationError } from '@opentdf/sdk';
8
- import {
9
- type HashAlgorithm,
10
- } from '@opentdf/sdk/singlecontainer';
11
-
12
- // Structural subset of fips-crypto-js. We keep this local to avoid importing
13
- // optional-peer types in non-FIPS bundles.
14
- type VirtruCryptoAlgorithm = {
15
- name: string;
16
- [key: string]: unknown;
17
- };
18
-
19
- type VirtruCryptoKeyLike = {
20
- release: () => void;
21
- [key: string]: unknown;
22
- };
23
-
24
- export type VirtruKeyUsage = 'encrypt' | 'decrypt' | 'sign' | 'verify';
25
-
26
- type VirtruCryptoLike = {
27
- isInitialized?: boolean;
28
- generateKey: (
29
- algorithm: VirtruCryptoAlgorithm,
30
- extractable: boolean,
31
- usages: readonly VirtruKeyUsage[],
32
- internal?: boolean
33
- ) => Promise<VirtruCryptoKeyLike>;
34
- digest: (
35
- algorithm: VirtruCryptoAlgorithm,
36
- data: Uint8Array
37
- ) => Promise<Uint8Array | ArrayBuffer>;
38
- encrypt: (
39
- algorithm: VirtruCryptoAlgorithm,
40
- key: VirtruCryptoKeyLike,
41
- data: Uint8Array
42
- ) => Promise<Uint8Array>;
43
- decrypt: (
44
- algorithm: VirtruCryptoAlgorithm,
45
- key: VirtruCryptoKeyLike,
46
- data: Uint8Array
47
- ) => Promise<Uint8Array>;
48
- sign: (
49
- algorithm: VirtruCryptoAlgorithm,
50
- key: VirtruCryptoKeyLike,
51
- data: Uint8Array
52
- ) => Promise<Uint8Array>;
53
- verify: (
54
- algorithm: VirtruCryptoAlgorithm,
55
- key: VirtruCryptoKeyLike,
56
- signature: Uint8Array,
57
- data: Uint8Array
58
- ) => Promise<boolean>;
59
- importKey: (
60
- format: 'raw' | 'spki' | 'pkcs8',
61
- keyData: Uint8Array,
62
- algorithm: VirtruCryptoAlgorithm,
63
- extractable: boolean,
64
- usages: readonly VirtruKeyUsage[]
65
- ) => Promise<VirtruCryptoKeyLike>;
66
- exportKey: (
67
- format: 'raw' | 'spki' | 'pkcs8',
68
- key: VirtruCryptoKeyLike
69
- ) => Promise<Uint8Array>;
70
- };
71
-
72
- // ─── VirtruCrypto Init ────────────────────────────────────────────────────────
73
-
74
- let virtruCryptoInstance: VirtruCryptoLike | null = null;
75
- let virtruCryptoInitPromise: Promise<void> | null = null;
76
-
77
- // fips-crypto-js initialization is asynchronous (WASM fetch + runtime init).
78
- // Keep this bounded so callers fail fast with a clear error if readiness never
79
- // occurs (missing wasm, blocked fetch, or runtime init failure).
80
- const VIRTRU_CRYPTO_INIT_TIMEOUT_MS = 10_000;
81
-
82
- // fips-crypto-js creates window.VirtruFipsWasmLib early, before the WASM
83
- // runtime finishes wiring methods. Treat it as ready only once initialized.
84
- function isVirtruCryptoInitialized(candidate: unknown): candidate is VirtruCryptoLike {
85
- if (!candidate || typeof candidate !== 'object') {
86
- return false;
87
- }
88
-
89
- const maybe = candidate as {
90
- isInitialized?: unknown;
91
- generateKey?: unknown;
92
- };
93
-
94
- return maybe.isInitialized === true && typeof maybe.generateKey === 'function';
95
- }
96
-
97
- export const getVirtruCrypto = async (): Promise<VirtruCryptoLike> => {
98
- if (typeof window === 'undefined') {
99
- throw new ConfigurationError(
100
- 'FIPS crypto requires a browser environment. VirtruCrypto WASM is not supported in Node.js or SSR contexts.'
101
- );
102
- }
103
-
104
- if (virtruCryptoInstance) return virtruCryptoInstance;
105
-
106
- if (virtruCryptoInitPromise) {
107
- await virtruCryptoInitPromise;
108
- return virtruCryptoInstance!;
109
- }
110
-
111
- virtruCryptoInitPromise = new Promise<void>((resolve, reject) => {
112
- // Fast path when another import already completed initialization.
113
- if (isVirtruCryptoInitialized((window as any).VirtruFipsWasmLib)) {
114
- resolve();
115
- return;
116
- }
117
-
118
- const onReady = () => {
119
- clearTimeout(timeout);
120
- resolve();
121
- };
122
-
123
- const timeout = setTimeout(
124
- () => {
125
- window.removeEventListener('wasmReady', onReady);
126
- reject(new Error('Timeout waiting for VirtruCrypto WASM initialization.'));
127
- },
128
- VIRTRU_CRYPTO_INIT_TIMEOUT_MS
129
- );
130
-
131
- // fips-crypto-js dispatches wasmReady on window (not document).
132
- window.addEventListener('wasmReady', onReady, { once: true });
133
-
134
- // Handle race: initialization may complete between the first check and the
135
- // event listener registration above.
136
- if (isVirtruCryptoInitialized((window as any).VirtruFipsWasmLib)) {
137
- window.removeEventListener('wasmReady', onReady);
138
- clearTimeout(timeout);
139
- resolve();
140
- }
141
- });
142
-
143
- await virtruCryptoInitPromise;
144
-
145
- virtruCryptoInstance = (window as any).VirtruFipsWasmLib;
146
- if (!isVirtruCryptoInitialized(virtruCryptoInstance)) {
147
- throw new Error('VirtruCrypto failed to initialize properly.');
148
- }
149
- return virtruCryptoInstance;
150
- };
151
-
152
- /**
153
- * Eagerly initialize FIPS WASM (used by DSP when `useFips: true`).
154
- * Loaded dynamically so non-FIPS consumers do not require the optional peer.
155
- */
156
- export const initializeFipsCrypto = (): void => {
157
- // Keep specifier dynamic so Vite/Rollup does not eagerly resolve the optional
158
- // peer in non-FIPS builds; load occurs only when FIPS mode is enabled.
159
- const modulePath = '@virtru-private/fips-crypto-js';
160
- void import(/* @vite-ignore */ modulePath).catch((err) => {
161
- console.error('Virtru FIPS crypto initialization failed: @virtru-private/fips-crypto-js is not installed.', err);
162
- });
163
- void getVirtruCrypto().catch((err) => {
164
- // Avoid unhandled rejections; same error appears on first crypto call too.
165
- console.error('Virtru FIPS crypto initialization failed:', err);
166
- });
167
- };
168
-
169
- // ─── Randomness / Digest ──────────────────────────────────────────────────────
170
-
171
- /**
172
- * Generate random bytes via WebCrypto CSPRNG.
173
- * fips-crypto-js only exposes fixed-length IV generation.
174
- *
175
- * Note: this does not route through the fips-crypto-js WASM boundary.
176
- * It should not be used when a strict "all cryptographic operations must be
177
- * performed inside the FIPS module" requirement applies.
178
- */
179
- export async function randomBytes(byteLength: number): Promise<Uint8Array> {
180
- const buf = new Uint8Array(byteLength);
181
- globalThis.crypto.getRandomValues(buf);
182
- return buf;
183
- }
184
-
185
- /**
186
- * Compute a hash digest. Only SHA-256 is supported by fips-crypto-js.
187
- */
188
- export async function digest(algorithm: HashAlgorithm, data: Uint8Array): Promise<Uint8Array> {
189
- if (algorithm !== 'SHA-256') {
190
- throw new ConfigurationError(
191
- `Hash algorithm '${algorithm}' not supported in FIPS mode. Only SHA-256 is supported.`
192
- );
193
- }
194
- const crypto = await getVirtruCrypto();
195
- const result = await crypto.digest({ name: 'SHA-256' }, data);
196
- return new Uint8Array(result);
197
- }