@virtru/dsp-sdk 0.4.1 → 0.6.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/CHANGELOG.md +15 -1
- package/README.md +81 -3
- package/dist/src/fips.d.ts +7 -0
- package/dist/src/fips.js +12 -0
- package/dist/src/lib/dsp.d.ts +17 -1
- package/dist/src/lib/dsp.js +28 -5
- package/dist/src/lib/fips-crypto/asymmetric.d.ts +35 -0
- package/dist/src/lib/fips-crypto/asymmetric.js +116 -0
- package/dist/src/lib/fips-crypto/key-format.d.ts +51 -0
- package/dist/src/lib/fips-crypto/key-format.js +233 -0
- package/dist/src/lib/fips-crypto/keys.d.ts +26 -0
- package/dist/src/lib/fips-crypto/keys.js +123 -0
- package/dist/src/lib/fips-crypto/primitives.d.ts +41 -0
- package/dist/src/lib/fips-crypto/primitives.js +105 -0
- package/dist/src/lib/fips-crypto/symmetric.d.ts +61 -0
- package/dist/src/lib/fips-crypto/symmetric.js +152 -0
- package/dist/src/lib/fips-crypto-service.d.ts +5 -0
- package/dist/src/lib/fips-crypto-service.js +44 -0
- package/dist/src/lib/utils.d.ts +8 -2
- package/dist/src/lib/utils.js +8 -9
- package/package.json +31 -5
- package/.prettierrc +0 -3
- package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_build.chunks.jsonl +0 -12
- package/.rush/temp/chunked-rush-logs/dsp-sdk._phase_test.chunks.jsonl +0 -43
- package/.rush/temp/package-deps__phase_build.json +0 -68
- package/.rush/temp/shrinkwrap-deps.json +0 -1257
- package/CHANGELOG.json +0 -278
- package/buf.gen.yaml +0 -17
- package/buf.yaml +0 -4
- package/config/jest.config.json +0 -6
- package/config/rig.json +0 -8
- package/dist/tests/dsp-client.test.d.ts +0 -1
- package/dist/tests/dsp-client.test.js +0 -73
- package/dist/tests/dsp.test.d.ts +0 -1
- package/dist/tests/dsp.test.js +0 -92
- package/dist/tests/mocks/create-export-artifacts.d.ts +0 -2
- package/dist/tests/mocks/create-export-artifacts.js +0 -13
- package/dist/tests/mocks/tagging-pdp-tag.d.ts +0 -2
- package/dist/tests/mocks/tagging-pdp-tag.js +0 -11
- package/dist/tests/mocks/well-known-configuration.d.ts +0 -2
- package/dist/tests/mocks/well-known-configuration.js +0 -12
- package/dist/tests/setup-msw.d.ts +0 -9
- package/dist/tests/setup-msw.js +0 -30
- package/eslint.config.mjs +0 -28
- package/lib-commonjs/src/gen/buf/validate/validate_pb.js +0 -487
- package/lib-commonjs/src/gen/config/v1/config_pb.js +0 -142
- package/lib-commonjs/src/gen/config/v1/kas_config_pb.js +0 -72
- package/lib-commonjs/src/gen/config/v1/meta_pb.js +0 -36
- package/lib-commonjs/src/gen/config/v1/outlook_config_pb.js +0 -67
- package/lib-commonjs/src/gen/config/v1/secureviewer_config_pb.js +0 -67
- package/lib-commonjs/src/gen/config/v1/tagging_pb.js +0 -246
- package/lib-commonjs/src/gen/google/api/annotations_pb.js +0 -33
- package/lib-commonjs/src/gen/google/api/http_pb.js +0 -44
- package/lib-commonjs/src/gen/policyimportexport/v1/policy_import_export_pb.js +0 -93
- package/lib-commonjs/src/gen/shared/v1/shared_pb.js +0 -95
- package/lib-commonjs/src/gen/tagging/pdp/v2/tagging_pb.js +0 -277
- package/lib-commonjs/src/gen/version/v1/version_pb.js +0 -40
- package/lib-commonjs/src/gen/virtru/common/common_pb.js +0 -76
- package/lib-commonjs/src/gen/virtru/policy/certificates/v1/certificates_pb.js +0 -77
- package/lib-commonjs/src/gen/virtru/policy/objects_pb.js +0 -143
- package/lib-commonjs/src/index.js +0 -48
- package/lib-commonjs/src/lib/client.js +0 -66
- package/lib-commonjs/src/lib/consts.js +0 -10
- package/lib-commonjs/src/lib/dsp.js +0 -49
- package/lib-commonjs/src/lib/utils.js +0 -402
- package/lib-commonjs/tests/dsp-client.test.js +0 -75
- package/lib-commonjs/tests/dsp.test.js +0 -94
- package/lib-commonjs/tests/mocks/create-export-artifacts.js +0 -17
- package/lib-commonjs/tests/mocks/tagging-pdp-tag.js +0 -15
- package/lib-commonjs/tests/mocks/well-known-configuration.js +0 -16
- package/lib-commonjs/tests/setup-msw.js +0 -33
- package/src/gen/buf/validate/validate_pb.ts +0 -4879
- package/src/gen/config/formatters/testdata/v1/test_pb.ts +0 -287
- package/src/gen/config/v1/config_connect.ts +0 -111
- package/src/gen/config/v1/config_pb.ts +0 -439
- package/src/gen/config/v1/kas_config_pb.ts +0 -183
- package/src/gen/config/v1/meta_pb.ts +0 -72
- package/src/gen/config/v1/outlook_config_pb.ts +0 -242
- package/src/gen/config/v1/secureviewer_config_pb.ts +0 -161
- package/src/gen/config/v1/tagging_pb.ts +0 -456
- package/src/gen/google/api/annotations_pb.ts +0 -43
- package/src/gen/google/api/http_pb.ts +0 -486
- package/src/gen/helloworld/v1/helloworld_connect.ts +0 -49
- package/src/gen/helloworld/v1/helloworld_pb.ts +0 -104
- package/src/gen/kas/nanotdf/v1/nanotdf_rewrap_connect.ts +0 -39
- package/src/gen/kas/nanotdf/v1/nanotdf_rewrap_pb.ts +0 -207
- package/src/gen/policyimportexport/v1/policy_import_export_connect.ts +0 -56
- package/src/gen/policyimportexport/v1/policy_import_export_pb.ts +0 -332
- package/src/gen/shared/v1/shared_connect.ts +0 -61
- package/src/gen/shared/v1/shared_pb.ts +0 -240
- package/src/gen/shared/v2/shared_connect.ts +0 -39
- package/src/gen/shared/v2/shared_pb.ts +0 -122
- package/src/gen/tagging/pdp/v2/externalprocessors/processor_connect.ts +0 -156
- package/src/gen/tagging/pdp/v2/externalprocessors/processor_pb.ts +0 -709
- package/src/gen/tagging/pdp/v2/stanag/stanag_pb.ts +0 -953
- package/src/gen/tagging/pdp/v2/tagging_connect.ts +0 -73
- package/src/gen/tagging/pdp/v2/tagging_pb.ts +0 -1064
- package/src/gen/version/v1/version_connect.ts +0 -31
- package/src/gen/version/v1/version_pb.ts +0 -143
- package/src/gen/virtru/common/common_pb.ts +0 -201
- package/src/gen/virtru/policy/certificates/v1/certificates_connect.ts +0 -44
- package/src/gen/virtru/policy/certificates/v1/certificates_pb.ts +0 -305
- package/src/gen/virtru/policy/objects_pb.ts +0 -253
- package/src/gen/web-admin/v1/config_connect.ts +0 -52
- package/src/gen/web-admin/v1/config_pb.ts +0 -173
- package/src/index.ts +0 -62
- package/src/lib/client.ts +0 -90
- package/src/lib/consts.ts +0 -8
- package/src/lib/dsp.ts +0 -65
- package/src/lib/utils.test.ts +0 -412
- package/src/lib/utils.ts +0 -524
- package/src/vite-env.d.ts +0 -7
- package/temp/build/lint/_eslint-5eVG3S6w.json +0 -30
- package/temp/test/jest/haste-map-b055a9550e6d9f9b43d8ad29b1607278-49d19aee56a0732eca9c014a51272338-8710993d07b75a801b0956e67ffc46fa +0 -0
- package/tests/dsp-client.test.ts +0 -95
- package/tests/dsp.test.ts +0 -119
- package/tests/mocks/create-export-artifacts.ts +0 -16
- package/tests/mocks/tagging-pdp-tag.ts +0 -13
- package/tests/mocks/well-known-configuration.ts +0 -15
- package/tests/setup-msw.ts +0 -35
- package/tsconfig.json +0 -32
- package/update-protos.sh +0 -63
- package/vitest.config.ts +0 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# Change Log - @virtru/dsp-sdk
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Thu, 12 Mar 2026 20:33:42 GMT and should not be manually modified.
|
|
4
|
+
|
|
5
|
+
## 0.6.0
|
|
6
|
+
Thu, 12 Mar 2026 20:33:42 GMT
|
|
7
|
+
|
|
8
|
+
### Minor changes
|
|
9
|
+
|
|
10
|
+
- expose fipsCryptoService implementation, useFips option
|
|
11
|
+
|
|
12
|
+
## 0.5.0
|
|
13
|
+
Thu, 12 Mar 2026 16:58:37 GMT
|
|
14
|
+
|
|
15
|
+
### Minor changes
|
|
16
|
+
|
|
17
|
+
- add FipsCryptoService implementation
|
|
4
18
|
|
|
5
19
|
## 0.4.1
|
|
6
20
|
Tue, 03 Mar 2026 16:24:41 GMT
|
package/README.md
CHANGED
|
@@ -79,16 +79,96 @@ console.log(decrypted); // "hello world"
|
|
|
79
79
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
### FIPS Mode
|
|
83
|
+
|
|
84
|
+
The SDK supports FIPS 140-2 compliant cryptography using a validated BoringSSL WASM module. To enable FIPS mode:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { AuthProviders, DSP } from '@virtru/dsp-sdk';
|
|
88
|
+
import { FipsCryptoService } from '@virtru/dsp-sdk/fips';
|
|
89
|
+
|
|
90
|
+
// Create auth provider with FIPS crypto service
|
|
91
|
+
const authProvider = await AuthProviders.clientSecretAuthProvider(
|
|
92
|
+
{
|
|
93
|
+
clientId: 'your-client-id',
|
|
94
|
+
clientSecret: 'your-client-secret',
|
|
95
|
+
oidcOrigin: 'https://oidc-endpoint',
|
|
96
|
+
exchange: 'client',
|
|
97
|
+
},
|
|
98
|
+
FipsCryptoService // Use FIPS-validated crypto
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Initialize DSP with FIPS mode enabled
|
|
102
|
+
const dsp = new DSP({
|
|
103
|
+
authProvider,
|
|
104
|
+
platformUrl: 'https://dsp-platform-url',
|
|
105
|
+
useFips: true, // Enables FIPS mode
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Check if FIPS is enabled
|
|
109
|
+
console.log('FIPS enabled:', dsp.fipsEnabled); // true
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
When FIPS mode is enabled, TDF3/ZTDF cryptographic operations (encryption, decryption, signing, key generation) use the FIPS-validated BoringSSL WASM module instead of Web Crypto API (`crypto.subtle`). NanoTDF operations are not covered by FIPS mode and continue to use `crypto.subtle`.
|
|
113
|
+
|
|
114
|
+
**Note:** FIPS mode only supports RSA keys; EC (Elliptic Curve) operations are not available in FIPS mode.
|
|
115
|
+
|
|
82
116
|
Refer to the API documentation for a complete list of available methods and configuration options.
|
|
83
117
|
|
|
84
118
|
## Testing
|
|
85
119
|
|
|
86
|
-
This section is intended for repository maintainers and contributors.
|
|
120
|
+
This section is intended for repository maintainers and contributors.
|
|
121
|
+
|
|
122
|
+
The test setup is split into:
|
|
123
|
+
- **Unit tests** (`test:unit`): run in Vitest `jsdom` mode and do **not** require Playwright.
|
|
124
|
+
- **Integration/browser tests** (`test:integration`): run in Chromium via Playwright.
|
|
125
|
+
|
|
126
|
+
To run the unit suite:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pnpm run build-fips-crypto
|
|
130
|
+
pnpm run copy-wasm
|
|
131
|
+
pnpm test:unit
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
To run the browser/integration suite:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pnpm test:integration
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
To run both:
|
|
87
141
|
|
|
88
142
|
```bash
|
|
89
143
|
pnpm test
|
|
90
144
|
```
|
|
91
145
|
|
|
146
|
+
### Integration Tests
|
|
147
|
+
|
|
148
|
+
Integration tests verify end-to-end encrypt/decrypt operations against a live DSP server, including FIPS mode compliance testing.
|
|
149
|
+
|
|
150
|
+
**Required environment variables:**
|
|
151
|
+
```bash
|
|
152
|
+
export DSP_PLATFORM_URL="http://localhost:8080" # DSP platform URL
|
|
153
|
+
export CLIENT_ID="your-client-id" # OAuth client ID
|
|
154
|
+
export CLIENT_SECRET="your-client-secret" # OAuth client secret
|
|
155
|
+
export OIDC_ENDPOINT="http://localhost:8888/auth/realms/opentdf" # OIDC endpoint
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Run integration tests:**
|
|
159
|
+
```bash
|
|
160
|
+
pnpm run build-fips-crypto
|
|
161
|
+
pnpm run copy-wasm
|
|
162
|
+
pnpm test:integration
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Run all tests (unit + integration):**
|
|
166
|
+
```bash
|
|
167
|
+
pnpm test
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Integration tests include FIPS compliance verification to ensure `crypto.subtle` is not used when FIPS mode is enabled (validating that only FIPS-certified BoringSSL WASM is used for cryptographic operations).
|
|
171
|
+
|
|
92
172
|
|
|
93
173
|
### Linting
|
|
94
174
|
|
|
@@ -165,5 +245,3 @@ You only need to regenerate the code when:
|
|
|
165
245
|
- Proto dependencies are updated
|
|
166
246
|
|
|
167
247
|
After regenerating, commit the updated files in `src/gen/` to version control.
|
|
168
|
-
|
|
169
|
-
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIPS entrypoint — only import this if @virtru-private/fips-crypto-js is installed.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* import { FipsCryptoService, initializeFipsCrypto } from '@virtru/dsp-sdk/fips';
|
|
6
|
+
*/
|
|
7
|
+
export { FipsCryptoService, initializeFipsCrypto, } from './lib/fips-crypto-service';
|
package/dist/src/fips.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
* FIPS entrypoint — only import this if @virtru-private/fips-crypto-js is installed.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { FipsCryptoService, initializeFipsCrypto } from '@virtru/dsp-sdk/fips';
|
|
11
|
+
*/
|
|
12
|
+
export { FipsCryptoService, initializeFipsCrypto, } from './lib/fips-crypto-service';
|
package/dist/src/lib/dsp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OpenTDF, type OpenTDFOptions } from '@opentdf/sdk';
|
|
1
|
+
import { OpenTDF, type OpenTDFOptions, type CryptoService } from '@opentdf/sdk';
|
|
2
2
|
import { type PlatformServices } from '@opentdf/sdk/platform';
|
|
3
3
|
import { type DSPClientServicesV1, type DSPClientServicesV2 } from './client';
|
|
4
4
|
import type { Interceptor } from '@connectrpc/connect';
|
|
@@ -7,8 +7,24 @@ export type DSPServicesV2 = DSPClientServicesV2;
|
|
|
7
7
|
export interface DSPOptions extends OpenTDFOptions {
|
|
8
8
|
platformUrl: string;
|
|
9
9
|
interceptors?: Interceptor[];
|
|
10
|
+
/**
|
|
11
|
+
* Enable FIPS 140-2 compliant cryptography for TDF3/ZTDF operations.
|
|
12
|
+
* When true, uses @virtru-private/fips-mode for encryption/decryption.
|
|
13
|
+
* Requires @virtru-private/fips-crypto-js WASM module to be loaded.
|
|
14
|
+
*
|
|
15
|
+
* Note: NanoTDF operations are NOT affected by this flag and will
|
|
16
|
+
* continue to use native crypto.subtle (not FIPS-compliant).
|
|
17
|
+
*/
|
|
18
|
+
useFips?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Optional: Provide a custom CryptoService implementation for TDF3/ZTDF.
|
|
21
|
+
* If provided, this takes precedence over useFips flag.
|
|
22
|
+
* NanoTDF operations are not affected.
|
|
23
|
+
*/
|
|
24
|
+
cryptoService?: CryptoService;
|
|
10
25
|
}
|
|
11
26
|
export declare class DSP extends OpenTDF {
|
|
27
|
+
readonly fipsEnabled: boolean;
|
|
12
28
|
readonly services: {
|
|
13
29
|
v1: DSPServicesV1;
|
|
14
30
|
v2: DSPServicesV2;
|
package/dist/src/lib/dsp.js
CHANGED
|
@@ -3,21 +3,44 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Your use of this file is governed by the Virtu Terms of Service <https://www.virtru.com/terms-of-service/>.
|
|
5
5
|
*/
|
|
6
|
-
import { OpenTDF } from '@opentdf/sdk';
|
|
6
|
+
import { OpenTDF, } from '@opentdf/sdk';
|
|
7
7
|
import { PlatformClient } from '@opentdf/sdk/platform';
|
|
8
8
|
import { DSPClient, } from './client';
|
|
9
|
+
import { FipsCryptoService, initializeFipsCrypto } from './fips-crypto-service';
|
|
9
10
|
export class DSP extends OpenTDF {
|
|
10
11
|
constructor(options) {
|
|
11
|
-
|
|
12
|
+
// Validate required options first
|
|
13
|
+
if (!options.platformUrl) {
|
|
14
|
+
throw new Error('platformUrl is required for DSP client');
|
|
15
|
+
}
|
|
16
|
+
// Configure FIPS crypto service for TDF3/ZTDF
|
|
17
|
+
const configuredOptions = { ...options };
|
|
18
|
+
if (options.useFips && options.cryptoService) {
|
|
19
|
+
console.warn('[DSP] Both useFips and cryptoService are set. useFips will be ignored — the provided cryptoService will be used.');
|
|
20
|
+
}
|
|
21
|
+
if (options.cryptoService) {
|
|
22
|
+
configuredOptions.cryptoService = options.cryptoService;
|
|
23
|
+
}
|
|
24
|
+
else if (options.useFips) {
|
|
25
|
+
initializeFipsCrypto();
|
|
26
|
+
configuredOptions.cryptoService = FipsCryptoService;
|
|
27
|
+
}
|
|
28
|
+
// Pass configured options to OpenTDF
|
|
29
|
+
super(configuredOptions);
|
|
30
|
+
Object.defineProperty(this, "fipsEnabled", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: void 0
|
|
35
|
+
});
|
|
12
36
|
Object.defineProperty(this, "services", {
|
|
13
37
|
enumerable: true,
|
|
14
38
|
configurable: true,
|
|
15
39
|
writable: true,
|
|
16
40
|
value: void 0
|
|
17
41
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
42
|
+
this.fipsEnabled = configuredOptions.cryptoService === FipsCryptoService;
|
|
43
|
+
// Setup DSP and Platform clients
|
|
21
44
|
const dspClient = new DSPClient({
|
|
22
45
|
platformUrl: options.platformUrl,
|
|
23
46
|
interceptors: options.interceptors,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Binary } from '@opentdf/sdk/singlecontainer';
|
|
2
|
+
import { type AsymmetricSigningAlgorithm, type ECCurve, type HkdfParams, type KeyPair, type PrivateKey, type PublicKey, type SymmetricKey } from '@opentdf/sdk/singlecontainer';
|
|
3
|
+
/**
|
|
4
|
+
* Generate an RSA key pair for encryption/decryption (RSA-OAEP).
|
|
5
|
+
*
|
|
6
|
+
* VirtruCrypto generates a single combined keypair handle. Both the PublicKey
|
|
7
|
+
* and PrivateKey opaque wrappers reference the same VirtruCryptoKey — the
|
|
8
|
+
* handle already carries all necessary usages. No export/re-import needed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateKeyPair(_size?: number): Promise<KeyPair>;
|
|
11
|
+
/**
|
|
12
|
+
* Generate an RSA key pair for signing/verification (RSASSA-PKCS1-v1_5).
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateSigningKeyPair(): Promise<KeyPair>;
|
|
15
|
+
/**
|
|
16
|
+
* Encrypt with RSA-OAEP public key.
|
|
17
|
+
* Accepts Binary payload or a SymmetricKey for key-wrapping.
|
|
18
|
+
*/
|
|
19
|
+
export declare function encryptWithPublicKey(payload: Binary | SymmetricKey, publicKey: PublicKey): Promise<Binary>;
|
|
20
|
+
/**
|
|
21
|
+
* Decrypt with RSA-OAEP private key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function decryptWithPrivateKey(encryptedPayload: Binary, privateKey: PrivateKey): Promise<Binary>;
|
|
24
|
+
/**
|
|
25
|
+
* Sign data with an RSA private key (RS256 only).
|
|
26
|
+
* VirtruCrypto requires pre-hashed data, so the data is SHA-256 hashed first.
|
|
27
|
+
*/
|
|
28
|
+
export declare function sign(data: Uint8Array, privateKey: PrivateKey, algorithm: AsymmetricSigningAlgorithm): Promise<Uint8Array>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify an RS256 signature.
|
|
31
|
+
* VirtruCrypto requires pre-hashed data.
|
|
32
|
+
*/
|
|
33
|
+
export declare function verify(data: Uint8Array, signature: Uint8Array, publicKey: PublicKey, algorithm: AsymmetricSigningAlgorithm): Promise<boolean>;
|
|
34
|
+
export declare function generateECKeyPair(_curve?: ECCurve): Promise<KeyPair>;
|
|
35
|
+
export declare function deriveKeyFromECDH(_privateKey: PrivateKey, _publicKey: PublicKey, _hkdfParams: HkdfParams): Promise<SymmetricKey>;
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
import { Binary } from '@opentdf/sdk/singlecontainer';
|
|
7
|
+
import {} from '@opentdf/sdk/singlecontainer';
|
|
8
|
+
import { ConfigurationError } from '@opentdf/sdk';
|
|
9
|
+
import { getVirtruCrypto } from './primitives';
|
|
10
|
+
import { isSymmetricKey, wrapKeyPair, unwrapPublicKey, unwrapPrivateKey, unwrapSymmetricKey } from './keys';
|
|
11
|
+
// ─── Key Generation ───────────────────────────────────────────────────────────
|
|
12
|
+
const ENC_DEC_METHODS = ['encrypt', 'decrypt'];
|
|
13
|
+
const SIGN_VERIFY_METHODS = ['sign', 'verify'];
|
|
14
|
+
const RSA_OAEP_PARAMS = {
|
|
15
|
+
name: 'RSA-OAEP',
|
|
16
|
+
modulusLength: 2048,
|
|
17
|
+
publicExponent: [1, 0, 1],
|
|
18
|
+
hash: 'SHA-256',
|
|
19
|
+
};
|
|
20
|
+
const RS256_PARAMS = {
|
|
21
|
+
name: 'RSASSA-PKCS1-v1_5',
|
|
22
|
+
modulusLength: 2048,
|
|
23
|
+
publicExponent: [1, 0, 1],
|
|
24
|
+
hash: 'SHA-256',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Generate an RSA key pair for encryption/decryption (RSA-OAEP).
|
|
28
|
+
*
|
|
29
|
+
* VirtruCrypto generates a single combined keypair handle. Both the PublicKey
|
|
30
|
+
* and PrivateKey opaque wrappers reference the same VirtruCryptoKey — the
|
|
31
|
+
* handle already carries all necessary usages. No export/re-import needed.
|
|
32
|
+
*/
|
|
33
|
+
export async function generateKeyPair(_size) {
|
|
34
|
+
const crypto = await getVirtruCrypto();
|
|
35
|
+
const combined = await crypto.generateKey(RSA_OAEP_PARAMS, true, ENC_DEC_METHODS);
|
|
36
|
+
return wrapKeyPair(combined, combined, 'rsa:2048');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate an RSA key pair for signing/verification (RSASSA-PKCS1-v1_5).
|
|
40
|
+
*/
|
|
41
|
+
export async function generateSigningKeyPair() {
|
|
42
|
+
const crypto = await getVirtruCrypto();
|
|
43
|
+
const combined = await crypto.generateKey(RS256_PARAMS, true, SIGN_VERIFY_METHODS);
|
|
44
|
+
return wrapKeyPair(combined, combined, 'rsa:2048');
|
|
45
|
+
}
|
|
46
|
+
// ─── RSA Encrypt / Decrypt ────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Encrypt with RSA-OAEP public key.
|
|
49
|
+
* Accepts Binary payload or a SymmetricKey for key-wrapping.
|
|
50
|
+
*/
|
|
51
|
+
export async function encryptWithPublicKey(payload, publicKey) {
|
|
52
|
+
const crypto = await getVirtruCrypto();
|
|
53
|
+
let payloadBytes;
|
|
54
|
+
if (isSymmetricKey(payload)) {
|
|
55
|
+
payloadBytes = await crypto.exportKey('raw', unwrapSymmetricKey(payload));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
payloadBytes = new Uint8Array(payload.asArrayBuffer());
|
|
59
|
+
}
|
|
60
|
+
const vKey = unwrapPublicKey(publicKey);
|
|
61
|
+
const encrypted = await crypto.encrypt(RSA_OAEP_PARAMS, vKey, payloadBytes);
|
|
62
|
+
return Binary.fromArrayBuffer(encrypted.buffer);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Decrypt with RSA-OAEP private key.
|
|
66
|
+
*/
|
|
67
|
+
export async function decryptWithPrivateKey(encryptedPayload, privateKey) {
|
|
68
|
+
const crypto = await getVirtruCrypto();
|
|
69
|
+
const vKey = unwrapPrivateKey(privateKey);
|
|
70
|
+
const payloadBytes = new Uint8Array(encryptedPayload.asArrayBuffer());
|
|
71
|
+
const decrypted = await crypto.decrypt(RSA_OAEP_PARAMS, vKey, payloadBytes);
|
|
72
|
+
return Binary.fromArrayBuffer(decrypted.buffer);
|
|
73
|
+
}
|
|
74
|
+
// ─── Sign / Verify ────────────────────────────────────────────────────────────
|
|
75
|
+
/**
|
|
76
|
+
* Sign data with an RSA private key (RS256 only).
|
|
77
|
+
* VirtruCrypto requires pre-hashed data, so the data is SHA-256 hashed first.
|
|
78
|
+
*/
|
|
79
|
+
export async function sign(data, privateKey, algorithm) {
|
|
80
|
+
if (algorithm !== 'RS256') {
|
|
81
|
+
throw new ConfigurationError(`Signing algorithm '${algorithm}' not supported in FIPS mode. Only RS256 is supported.`);
|
|
82
|
+
}
|
|
83
|
+
const crypto = await getVirtruCrypto();
|
|
84
|
+
const vKey = unwrapPrivateKey(privateKey);
|
|
85
|
+
// VirtruCrypto signs a pre-computed hash, not raw data
|
|
86
|
+
const hash = await crypto.digest({ name: 'SHA-256' }, data);
|
|
87
|
+
const sig = await crypto.sign(RS256_PARAMS, vKey, new Uint8Array(hash));
|
|
88
|
+
return new Uint8Array(sig);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Verify an RS256 signature.
|
|
92
|
+
* VirtruCrypto requires pre-hashed data.
|
|
93
|
+
*/
|
|
94
|
+
export async function verify(data, signature, publicKey, algorithm) {
|
|
95
|
+
if (algorithm !== 'RS256') {
|
|
96
|
+
throw new ConfigurationError(`Verification algorithm '${algorithm}' not supported in FIPS mode. Only RS256 is supported.`);
|
|
97
|
+
}
|
|
98
|
+
const crypto = await getVirtruCrypto();
|
|
99
|
+
const vKey = unwrapPublicKey(publicKey);
|
|
100
|
+
const hash = await crypto.digest({ name: 'SHA-256' }, data);
|
|
101
|
+
try {
|
|
102
|
+
return await crypto.verify(RS256_PARAMS, vKey, signature, new Uint8Array(hash));
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
if (e === 'Signature verification error')
|
|
106
|
+
return false;
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ─── EC Stubs (unsupported in FIPS mode) ─────────────────────────────────────
|
|
111
|
+
export async function generateECKeyPair(_curve) {
|
|
112
|
+
throw new ConfigurationError('EC key generation is not supported in FIPS mode.');
|
|
113
|
+
}
|
|
114
|
+
export async function deriveKeyFromECDH(_privateKey, _publicKey, _hkdfParams) {
|
|
115
|
+
throw new ConfigurationError('ECDH key derivation is not supported in FIPS mode.');
|
|
116
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type KeyOptions, type PrivateKey, type PublicKey, type PublicKeyInfo } from '@opentdf/sdk/singlecontainer';
|
|
2
|
+
/**
|
|
3
|
+
* Import a PEM public key (SPKI) as an opaque PublicKey.
|
|
4
|
+
* Only RSA keys are supported in FIPS mode.
|
|
5
|
+
*
|
|
6
|
+
* @param pem - PEM-encoded public key (`-----BEGIN PUBLIC KEY-----`)
|
|
7
|
+
* @param options.usage - `'encrypt'` (RSA-OAEP) or `'sign'` (RS256 verify). Defaults to `'encrypt'`.
|
|
8
|
+
* @param options.extractable - Defaults to `true`.
|
|
9
|
+
* @param options.algorithmHint - Skips SPKI parsing when provided.
|
|
10
|
+
*/
|
|
11
|
+
export declare function importPublicKey(pem: string, options: KeyOptions): Promise<PublicKey>;
|
|
12
|
+
/**
|
|
13
|
+
* Import a PEM private key (PKCS#8) as an opaque PrivateKey.
|
|
14
|
+
* Only RSA keys are supported in FIPS mode.
|
|
15
|
+
*
|
|
16
|
+
* @param pem - PEM-encoded private key (`-----BEGIN PRIVATE KEY-----`)
|
|
17
|
+
* @param options.usage - `'encrypt'` (RSA-OAEP decrypt) or `'sign'` (RS256). Defaults to `'encrypt'`.
|
|
18
|
+
* @param options.extractable - Defaults to `false`.
|
|
19
|
+
* @param options.algorithmHint - Algorithm label to attach. Defaults to `'rsa:2048'`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function importPrivateKey(pem: string, options: KeyOptions): Promise<PrivateKey>;
|
|
22
|
+
/**
|
|
23
|
+
* Export an opaque public key to PEM (SPKI / `-----BEGIN PUBLIC KEY-----`).
|
|
24
|
+
* Handles VirtruCrypto's PKCS#1 output for generated keys transparently.
|
|
25
|
+
*/
|
|
26
|
+
export declare function exportPublicKeyPem(key: PublicKey): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Export an opaque public key to JWK.
|
|
29
|
+
* Uses WebCrypto for the JWK conversion after normalizing to SPKI.
|
|
30
|
+
*/
|
|
31
|
+
export declare function exportPublicKeyJwk(key: PublicKey): Promise<JsonWebKey>;
|
|
32
|
+
/**
|
|
33
|
+
* Export an opaque private key to PEM (PKCS#8 / `-----BEGIN PRIVATE KEY-----`).
|
|
34
|
+
*
|
|
35
|
+
* FOR TESTING / DEVELOPMENT ONLY — not included in the CryptoService interface.
|
|
36
|
+
* Only works for keys that were imported via importPrivateKey (PKCS#8 format).
|
|
37
|
+
* Generated private keys cannot be exported from the FIPS keystore.
|
|
38
|
+
*/
|
|
39
|
+
export declare function exportPrivateKeyPem(key: PrivateKey): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Use the WebCrypto API to parse a PEM public key and extract its algorithm info.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parsePublicKeyPem(pem: string): Promise<PublicKeyInfo>;
|
|
44
|
+
/**
|
|
45
|
+
* Use the WebCrypto API to extract the public key PEM from a certificate or PEM string.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractPublicKeyPem(certOrPem: string): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Use the WebCrypto API to convert a JWK public key to PEM format.
|
|
50
|
+
*/
|
|
51
|
+
export declare function jwkToPublicKeyPem(jwk: JsonWebKey): Promise<string>;
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
import { WebCryptoService, } from '@opentdf/sdk/singlecontainer';
|
|
7
|
+
import { ConfigurationError } from '@opentdf/sdk';
|
|
8
|
+
import { formatAsPem, removePemFormatting, guessAlgorithmName } from '@opentdf/sdk/cryptoutils';
|
|
9
|
+
import { base64, hex } from '@opentdf/sdk/encodings';
|
|
10
|
+
import { getVirtruCrypto } from './primitives';
|
|
11
|
+
import { wrapPublicKey, wrapPrivateKey, unwrapPublicKey, unwrapPrivateKey } from './keys';
|
|
12
|
+
const VERIFY_USAGE = ['verify'];
|
|
13
|
+
const ENCRYPT_VERIFY_USAGES = ['encrypt', 'verify'];
|
|
14
|
+
const SIGN_USAGE = ['sign'];
|
|
15
|
+
const DECRYPT_USAGE = ['decrypt'];
|
|
16
|
+
// ─── PEM Utilities ────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Strip PEM headers/footers and whitespace, decode base64 → raw DER bytes.
|
|
19
|
+
*/
|
|
20
|
+
function pemToBytes(pem) {
|
|
21
|
+
return new Uint8Array(base64.decodeArrayBuffer(removePemFormatting(pem)));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse the RSA modulus bit length from SPKI-formatted DER bytes.
|
|
25
|
+
* Walks: SEQUENCE { AlgorithmIdentifier, BIT STRING { RSAPublicKey { modulus INTEGER } } }
|
|
26
|
+
*/
|
|
27
|
+
function rsaModulusBitsFromSpki(spki) {
|
|
28
|
+
let pos = 0;
|
|
29
|
+
const readLen = () => {
|
|
30
|
+
let len = spki[pos++];
|
|
31
|
+
if (len & 0x80) {
|
|
32
|
+
const n = len & 0x7f;
|
|
33
|
+
len = 0;
|
|
34
|
+
for (let i = 0; i < n; i++)
|
|
35
|
+
len = (len << 8) | spki[pos++];
|
|
36
|
+
}
|
|
37
|
+
return len;
|
|
38
|
+
};
|
|
39
|
+
// SPKI outer SEQUENCE
|
|
40
|
+
pos++;
|
|
41
|
+
readLen();
|
|
42
|
+
// Skip AlgorithmIdentifier SEQUENCE
|
|
43
|
+
pos++;
|
|
44
|
+
pos += readLen();
|
|
45
|
+
// BIT STRING
|
|
46
|
+
pos++;
|
|
47
|
+
readLen();
|
|
48
|
+
pos++; // unused-bits byte
|
|
49
|
+
// RSAPublicKey SEQUENCE
|
|
50
|
+
pos++;
|
|
51
|
+
readLen();
|
|
52
|
+
// modulus INTEGER tag + length
|
|
53
|
+
pos++;
|
|
54
|
+
const modulusLen = readLen();
|
|
55
|
+
// Leading 0x00 is a sign byte for positive INTEGERs — exclude it
|
|
56
|
+
const padding = spki[pos] === 0x00 ? 1 : 0;
|
|
57
|
+
return (modulusLen - padding) * 8;
|
|
58
|
+
}
|
|
59
|
+
// ─── ASN.1 Helpers ────────────────────────────────────────────────────────────
|
|
60
|
+
function asn1EncodeLength(len) {
|
|
61
|
+
if (len < 0x80)
|
|
62
|
+
return new Uint8Array([len]);
|
|
63
|
+
if (len < 0x100)
|
|
64
|
+
return new Uint8Array([0x81, len]);
|
|
65
|
+
return new Uint8Array([0x82, (len >> 8) & 0xff, len & 0xff]);
|
|
66
|
+
}
|
|
67
|
+
function asn1Concat(arrays) {
|
|
68
|
+
const total = arrays.reduce((s, a) => s + a.length, 0);
|
|
69
|
+
const out = new Uint8Array(total);
|
|
70
|
+
let off = 0;
|
|
71
|
+
for (const a of arrays) {
|
|
72
|
+
out.set(a, off);
|
|
73
|
+
off += a.length;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function asn1Sequence(elements) {
|
|
78
|
+
const body = asn1Concat(elements);
|
|
79
|
+
return asn1Concat([new Uint8Array([0x30]), asn1EncodeLength(body.length), body]);
|
|
80
|
+
}
|
|
81
|
+
function asn1BitString(data) {
|
|
82
|
+
const inner = asn1Concat([new Uint8Array([0x00]), data]); // 0x00 = zero unused bits
|
|
83
|
+
return asn1Concat([new Uint8Array([0x03]), asn1EncodeLength(inner.length), inner]);
|
|
84
|
+
}
|
|
85
|
+
// RSA AlgorithmIdentifier: SEQUENCE { OID 1.2.840.113549.1.1.1, NULL }
|
|
86
|
+
const RSA_ALG_ID = asn1Sequence([
|
|
87
|
+
new Uint8Array([0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01]),
|
|
88
|
+
new Uint8Array([0x05, 0x00]),
|
|
89
|
+
]);
|
|
90
|
+
/**
|
|
91
|
+
* Returns true if the DER bytes are already SPKI format (have an AlgorithmIdentifier OID).
|
|
92
|
+
*/
|
|
93
|
+
function isSpkiFormat(derBytes) {
|
|
94
|
+
const hexStr = hex.encodeArrayBuffer(derBytes.buffer);
|
|
95
|
+
try {
|
|
96
|
+
guessAlgorithmName(hexStr);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wraps a bare RSAPublicKey (PKCS#1) blob in an SPKI AlgorithmIdentifier container.
|
|
105
|
+
* VirtruCrypto returns PKCS#1 for generated keys; imported keys are already SPKI.
|
|
106
|
+
*/
|
|
107
|
+
function normalizePublicKeyToSpki(exported) {
|
|
108
|
+
if (isSpkiFormat(exported))
|
|
109
|
+
return exported;
|
|
110
|
+
return asn1Sequence([RSA_ALG_ID, asn1BitString(exported)]);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Wraps a bare RSAPrivateKey (PKCS#1) blob in a PKCS#8 PrivateKeyInfo container.
|
|
114
|
+
* VirtruCrypto's exportKey('pkcs8') returns raw PKCS#1 DER — the 'pkcs8' flag
|
|
115
|
+
* just selects "export private key" vs "export public key" from the combined slot.
|
|
116
|
+
* We wrap it here to produce standard PKCS#8:
|
|
117
|
+
* SEQUENCE { INTEGER 0, AlgorithmIdentifier, OCTET STRING { RSAPrivateKey } }
|
|
118
|
+
*/
|
|
119
|
+
function normalizePrivateKeyToPkcs8(pkcs1) {
|
|
120
|
+
const version = new Uint8Array([0x02, 0x01, 0x00]); // INTEGER 0
|
|
121
|
+
const privateKeyOctetString = asn1Concat([
|
|
122
|
+
new Uint8Array([0x04]),
|
|
123
|
+
asn1EncodeLength(pkcs1.length),
|
|
124
|
+
pkcs1,
|
|
125
|
+
]);
|
|
126
|
+
return asn1Sequence([version, RSA_ALG_ID, privateKeyOctetString]);
|
|
127
|
+
}
|
|
128
|
+
// ─── Key Import ───────────────────────────────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Import a PEM public key (SPKI) as an opaque PublicKey.
|
|
131
|
+
* Only RSA keys are supported in FIPS mode.
|
|
132
|
+
*
|
|
133
|
+
* @param pem - PEM-encoded public key (`-----BEGIN PUBLIC KEY-----`)
|
|
134
|
+
* @param options.usage - `'encrypt'` (RSA-OAEP) or `'sign'` (RS256 verify). Defaults to `'encrypt'`.
|
|
135
|
+
* @param options.extractable - Defaults to `true`.
|
|
136
|
+
* @param options.algorithmHint - Skips SPKI parsing when provided.
|
|
137
|
+
*/
|
|
138
|
+
export async function importPublicKey(pem, options) {
|
|
139
|
+
const { usage = 'encrypt', extractable = true, algorithmHint } = options;
|
|
140
|
+
const keyBytes = pemToBytes(pem);
|
|
141
|
+
let algorithm;
|
|
142
|
+
if (algorithmHint) {
|
|
143
|
+
algorithm = algorithmHint;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const modulusBits = rsaModulusBitsFromSpki(keyBytes);
|
|
147
|
+
algorithm = modulusBits <= 2048 ? 'rsa:2048' : 'rsa:4096';
|
|
148
|
+
}
|
|
149
|
+
if (!algorithm.startsWith('rsa:')) {
|
|
150
|
+
throw new ConfigurationError(`Key algorithm '${algorithm}' not supported in FIPS mode. Only RSA keys are supported.`);
|
|
151
|
+
}
|
|
152
|
+
// VirtruCrypto uses RSASSA-PKCS1-v1_5 as the import algorithm name for all RSA keys;
|
|
153
|
+
// the per-operation algorithm (RSA-OAEP vs PKCS1v15) is specified at call time.
|
|
154
|
+
const usages = usage === 'sign' ? VERIFY_USAGE : ENCRYPT_VERIFY_USAGES;
|
|
155
|
+
const crypto = await getVirtruCrypto();
|
|
156
|
+
const vKey = await crypto.importKey('spki', keyBytes, { name: 'RSASSA-PKCS1-v1_5' }, extractable, usages);
|
|
157
|
+
return wrapPublicKey(vKey, algorithm);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Import a PEM private key (PKCS#8) as an opaque PrivateKey.
|
|
161
|
+
* Only RSA keys are supported in FIPS mode.
|
|
162
|
+
*
|
|
163
|
+
* @param pem - PEM-encoded private key (`-----BEGIN PRIVATE KEY-----`)
|
|
164
|
+
* @param options.usage - `'encrypt'` (RSA-OAEP decrypt) or `'sign'` (RS256). Defaults to `'encrypt'`.
|
|
165
|
+
* @param options.extractable - Defaults to `false`.
|
|
166
|
+
* @param options.algorithmHint - Algorithm label to attach. Defaults to `'rsa:2048'`.
|
|
167
|
+
*/
|
|
168
|
+
export async function importPrivateKey(pem, options) {
|
|
169
|
+
const { usage = 'encrypt', extractable = false, algorithmHint } = options;
|
|
170
|
+
const keyBytes = pemToBytes(pem);
|
|
171
|
+
const algorithm = algorithmHint ?? 'rsa:2048';
|
|
172
|
+
if (!algorithm.startsWith('rsa:')) {
|
|
173
|
+
throw new ConfigurationError(`Key algorithm '${algorithm}' not supported in FIPS mode. Only RSA keys are supported.`);
|
|
174
|
+
}
|
|
175
|
+
const usages = usage === 'sign' ? SIGN_USAGE : DECRYPT_USAGE;
|
|
176
|
+
const crypto = await getVirtruCrypto();
|
|
177
|
+
const vKey = await crypto.importKey('pkcs8', keyBytes, { name: 'RSASSA-PKCS1-v1_5' }, extractable, usages);
|
|
178
|
+
return wrapPrivateKey(vKey, algorithm);
|
|
179
|
+
}
|
|
180
|
+
// ─── Key Export ───────────────────────────────────────────────────────────────
|
|
181
|
+
/**
|
|
182
|
+
* Export an opaque public key to PEM (SPKI / `-----BEGIN PUBLIC KEY-----`).
|
|
183
|
+
* Handles VirtruCrypto's PKCS#1 output for generated keys transparently.
|
|
184
|
+
*/
|
|
185
|
+
export async function exportPublicKeyPem(key) {
|
|
186
|
+
const crypto = await getVirtruCrypto();
|
|
187
|
+
const exported = await crypto.exportKey('spki', unwrapPublicKey(key));
|
|
188
|
+
const spki = normalizePublicKeyToSpki(exported);
|
|
189
|
+
return formatAsPem(spki.buffer, 'PUBLIC KEY');
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Export an opaque public key to JWK.
|
|
193
|
+
* Uses WebCrypto for the JWK conversion after normalizing to SPKI.
|
|
194
|
+
*/
|
|
195
|
+
export async function exportPublicKeyJwk(key) {
|
|
196
|
+
const crypto = await getVirtruCrypto();
|
|
197
|
+
const exported = await crypto.exportKey('spki', unwrapPublicKey(key));
|
|
198
|
+
const spki = normalizePublicKeyToSpki(exported);
|
|
199
|
+
const webKey = await globalThis.crypto.subtle.importKey('spki', spki, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['verify']);
|
|
200
|
+
return globalThis.crypto.subtle.exportKey('jwk', webKey);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Export an opaque private key to PEM (PKCS#8 / `-----BEGIN PRIVATE KEY-----`).
|
|
204
|
+
*
|
|
205
|
+
* FOR TESTING / DEVELOPMENT ONLY — not included in the CryptoService interface.
|
|
206
|
+
* Only works for keys that were imported via importPrivateKey (PKCS#8 format).
|
|
207
|
+
* Generated private keys cannot be exported from the FIPS keystore.
|
|
208
|
+
*/
|
|
209
|
+
export async function exportPrivateKeyPem(key) {
|
|
210
|
+
const crypto = await getVirtruCrypto();
|
|
211
|
+
const exported = await crypto.exportKey('pkcs8', unwrapPrivateKey(key));
|
|
212
|
+
const pkcs8 = normalizePrivateKeyToPkcs8(exported);
|
|
213
|
+
return formatAsPem(pkcs8.buffer, 'PRIVATE KEY');
|
|
214
|
+
}
|
|
215
|
+
// ─── Public Key Utils ─────────────────────────────────────
|
|
216
|
+
/**
|
|
217
|
+
* Use the WebCrypto API to parse a PEM public key and extract its algorithm info.
|
|
218
|
+
*/
|
|
219
|
+
export async function parsePublicKeyPem(pem) {
|
|
220
|
+
return WebCryptoService.parsePublicKeyPem(pem);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Use the WebCrypto API to extract the public key PEM from a certificate or PEM string.
|
|
224
|
+
*/
|
|
225
|
+
export async function extractPublicKeyPem(certOrPem) {
|
|
226
|
+
return WebCryptoService.extractPublicKeyPem(certOrPem);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Use the WebCrypto API to convert a JWK public key to PEM format.
|
|
230
|
+
*/
|
|
231
|
+
export async function jwkToPublicKeyPem(jwk) {
|
|
232
|
+
return WebCryptoService.jwkToPublicKeyPem(jwk);
|
|
233
|
+
}
|