@aurabx/jmix-js 0.1.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/.ai/security.md +184 -0
- package/.eslintrc.cjs +23 -0
- package/.idea/jmix-ts.iml +13 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.prettierrc +9 -0
- package/README.md +335 -0
- package/WARP.md +143 -0
- package/dist/JmixBuilder.d.ts +82 -0
- package/dist/JmixBuilder.d.ts.map +1 -0
- package/dist/JmixBuilder.js +353 -0
- package/dist/JmixBuilder.js.map +1 -0
- package/dist/crypto/PayloadDecryptor.d.ts +12 -0
- package/dist/crypto/PayloadDecryptor.d.ts.map +1 -0
- package/dist/crypto/PayloadDecryptor.js +39 -0
- package/dist/crypto/PayloadDecryptor.js.map +1 -0
- package/dist/crypto/PayloadEncryptor.d.ts +21 -0
- package/dist/crypto/PayloadEncryptor.d.ts.map +1 -0
- package/dist/crypto/PayloadEncryptor.js +79 -0
- package/dist/crypto/PayloadEncryptor.js.map +1 -0
- package/dist/demo-decrypt-existing.js +27 -0
- package/dist/demo-decrypt.js +39 -0
- package/dist/demo-no-validation.js +95 -0
- package/dist/demo-package-encrypted.js +34 -0
- package/dist/demo-package.js +28 -0
- package/dist/demo-verify-hash.js +30 -0
- package/dist/demo.js +73 -0
- package/dist/dicom/DicomProcessor.d.ts +40 -0
- package/dist/dicom/DicomProcessor.d.ts.map +1 -0
- package/dist/dicom/DicomProcessor.js +231 -0
- package/dist/dicom/DicomProcessor.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +165 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +31 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validation/SchemaValidator.d.ts +25 -0
- package/dist/validation/SchemaValidator.d.ts.map +1 -0
- package/dist/validation/SchemaValidator.js +125 -0
- package/dist/validation/SchemaValidator.js.map +1 -0
- package/jest.config.json +20 -0
- package/package.json +74 -0
- package/samples/sample_config.json +52 -0
- package/samples/study_1/series_1/CT.1.1.dcm +0 -0
- package/samples/study_1/series_1/CT.1.2.dcm +0 -0
- package/samples/study_1/series_1/CT.1.3.dcm +0 -0
- package/samples/study_1/series_1/CT.1.4.dcm +0 -0
- package/samples/study_1/series_1/CT.1.5.dcm +0 -0
- package/samples/study_1/series_2/CT.2.1.dcm +0 -0
- package/samples/study_1/series_2/CT.2.2.dcm +0 -0
- package/samples/study_1/series_2/CT.2.3.dcm +0 -0
- package/samples/study_1/series_2/CT.2.4.dcm +0 -0
- package/samples/study_1/series_2/CT.2.5.dcm +0 -0
- package/samples/study_1/series_3/CT.3.1.dcm +0 -0
- package/samples/study_1/series_3/CT.3.2.dcm +0 -0
- package/samples/study_1/series_3/CT.3.3.dcm +0 -0
- package/samples/study_1/series_3/CT.3.4.dcm +0 -0
- package/samples/study_1/series_3/CT.3.5.dcm +0 -0
- package/src/JmixBuilder.ts +572 -0
- package/src/crypto/PayloadDecryptor.ts +45 -0
- package/src/crypto/PayloadEncryptor.ts +97 -0
- package/src/dicom/DicomProcessor.ts +262 -0
- package/src/index.ts +30 -0
- package/src/types/daikon.d.ts +1 -0
- package/src/types/index.ts +201 -0
- package/src/validation/SchemaValidator.ts +156 -0
- package/test-encrypt.js +10 -0
- package/tests/JmixBuilder.test.ts +170 -0
- package/tests/SchemaValidator.test.ts +196 -0
- package/tsconfig.json +21 -0
package/.ai/security.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# JMIX Envelope Security White Paper
|
|
2
|
+
|
|
3
|
+
Christopher Skene
|
|
4
|
+
8th June 2025
|
|
5
|
+
DRAFT - WORK IN PROGRESS
|
|
6
|
+
|
|
7
|
+
## Abstract
|
|
8
|
+
|
|
9
|
+
This paper presents the security encryption architecture for JMIX (JSON Medical Imaging Exchange) Envelopes, supporting secure, peer-to-peer exchange of identifiable imaging data. The model is designed to support interoperability between independent actors and optionally leverage the Aurabox directory and services for enhanced usability, policy enforcement, and auditability, without introducing any dependency on Aurabox for decryption or transmission.
|
|
10
|
+
|
|
11
|
+
## 1. Introduction
|
|
12
|
+
|
|
13
|
+
Healthcare imaging data exchange faces challenges around security, interoperability, and decentralisation. Traditional centralised exchange models introduce unnecessary dependencies and trust anchors, whereas pure peer-to-peer systems can lack usability or governance.
|
|
14
|
+
|
|
15
|
+
JMIX aims to strike a balance: enabling direct, secure, peer-to-peer sharing, while allowing parties who choose to use a trusted directory (e.g. Aurabox) to gain benefits like key discovery, consent validation, and identity anchoring.
|
|
16
|
+
|
|
17
|
+
Some of the features of this security model include:
|
|
18
|
+
|
|
19
|
+
- Forward Secrecy: Ephemeral keys ensure compromise of long-term keys does not expose past data.
|
|
20
|
+
- Authentication: GCM tag ensures ciphertext cannot be tampered with.
|
|
21
|
+
- Minimal Trust: No dependency on Aurabox or any other service for basic operation.
|
|
22
|
+
- Optional Governance: Aurabox can be used to anchor identities, resolve key IDs, or audit transfers
|
|
23
|
+
- Non-repudiation: Proof of sender
|
|
24
|
+
|
|
25
|
+
## 2. Design Principles
|
|
26
|
+
|
|
27
|
+
Self-contained encryption: All JMIX envelopes must be decryptable using only what is contained inside the envelope, assuming the recipient possesses the correct private key.
|
|
28
|
+
|
|
29
|
+
Ephemeral key use: All encryption must use ephemeral sender keys to provide forward secrecy.
|
|
30
|
+
|
|
31
|
+
Fixed cryptographic stack: All payloads are encrypted using AES-256-GCM with ECDH key agreement and HKDF (SHA-256) for symmetric key derivation.
|
|
32
|
+
|
|
33
|
+
Directory-optional context: Key discovery, identity mapping, and policy evaluation may optionally be facilitated by Aurabox or a compatible directory, but are not required for decryption.
|
|
34
|
+
|
|
35
|
+
## 3. Cryptographic Foundations
|
|
36
|
+
|
|
37
|
+
Algorithm:
|
|
38
|
+
|
|
39
|
+
- Encryption: AES-256 in Galois/Counter Mode (GCM)
|
|
40
|
+
- Key Exchange: Elliptic Curve Diffie-Hellman (ECDH) over Curve25519
|
|
41
|
+
- Key Derivation: HKDF using SHA-256
|
|
42
|
+
|
|
43
|
+
Each package is encrypted using a one-time symmetric key derived from ECDH, ensuring forward secrecy and resilience.
|
|
44
|
+
|
|
45
|
+
## 4. Envelope Structure
|
|
46
|
+
|
|
47
|
+
### a. Encryption (required when the package is encrypted)
|
|
48
|
+
|
|
49
|
+
The Encryption block describes the encryption of the package directory. It is required when the package is encrypted, but not when the package is unencrypted (for example, if the envelope is being used as a download).
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
"encryption": {
|
|
53
|
+
"algorithm": "AES-256-GCM",
|
|
54
|
+
"ephemeral_public_key": "<base64>",
|
|
55
|
+
"iv": "<base64>",
|
|
56
|
+
"auth_tag": "<base64>"
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
These fields are all required for the recipient to decrypt the contents of the package directory.
|
|
61
|
+
|
|
62
|
+
- algorithm: Always AES-256-GCM
|
|
63
|
+
- ephemeral_public_key: The sender's one-time-use ECDH public key.
|
|
64
|
+
- iv: 96-bit AES-GCM nonce.
|
|
65
|
+
- auth_tag: 128-bit authentication tag generated during encryption.
|
|
66
|
+
|
|
67
|
+
### b. Sender Assertion (optional, but recommended)
|
|
68
|
+
|
|
69
|
+
The Sender Assertion block allows the sender to provide information about themselves, so that the receiver can verify the authenticity of the package. It serves as a mechanism for authenticating and verifying the identity of the sender of a JMIX envelope, especially in decentralised or federated environments.
|
|
70
|
+
|
|
71
|
+
It helps guard against spoofing by cryptographically binding the claimed identity to the encrypted payload.
|
|
72
|
+
Enables trust decisions in the absence of live infrastructure (e.g., offline environments).
|
|
73
|
+
Supports forensic verification: recipients can prove to others who sent the data and that it hasn’t been altered.
|
|
74
|
+
|
|
75
|
+
The sender_assertion block exists to:
|
|
76
|
+
|
|
77
|
+
- Provide verifiable metadata about the sender, such as their claimed identity and the cryptographic evidence that supports that claim.
|
|
78
|
+
- Enable trust and non-repudiation, by binding the sender’s claimed identity to the specific encrypted payload using a signature.
|
|
79
|
+
- Support optional directory-based verification, such as through Aurabox or other compatible directories, without being dependent on them.
|
|
80
|
+
|
|
81
|
+
This is crucial in peer-to-peer settings, where there is no central server to authenticate participants. It allows recipients and third parties (for example, auditors) to validate who sent a JMIX package, even offline. The Sender Assertion is foundational to JMIX's trust and governance model, acting as a decentralised and optionally third-party-verified sender identity proof, ensuring secure and accountable medical imaging exchange.
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"signing_key": {
|
|
86
|
+
"alg": "Ed25519",
|
|
87
|
+
"public_key": "<base64>",
|
|
88
|
+
"fingerprint": "SHA256:<hex>"
|
|
89
|
+
},
|
|
90
|
+
"key_reference": "aurabox://org/clinic-a#key-ed25519",
|
|
91
|
+
"signed_fields": [
|
|
92
|
+
"sender.id",
|
|
93
|
+
"sender.name",
|
|
94
|
+
"signatures.manifest.kid",
|
|
95
|
+
"manifest_hash"
|
|
96
|
+
],
|
|
97
|
+
"signature": "<base64sig>",
|
|
98
|
+
"expires_at": "2025-07-07T00:00:00Z",
|
|
99
|
+
"directory_attestation": {
|
|
100
|
+
"provider": "aurabox",
|
|
101
|
+
"attestation_signature": "<JWS>",
|
|
102
|
+
"attestation_timestamp": "2025-06-07T14:01:00Z",
|
|
103
|
+
"attestation_public_key": "<base64>"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The file is referenced in the sender block, as follows:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
"sender": {
|
|
112
|
+
"id": "org:au.gov.health.123456",
|
|
113
|
+
"name": "Clinic A",
|
|
114
|
+
"contact": "dicom@clinica.org.au",
|
|
115
|
+
"assertion": {
|
|
116
|
+
"...": "assertion goes here"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
While the attestation includes expiry, the receiver is not bound to reject the envelope if the expiry has passed. This is up to receivers to define.
|
|
122
|
+
|
|
123
|
+
### c. Requester Assertion (optional)
|
|
124
|
+
|
|
125
|
+
The requester assertion follows a similar pattern to the Sender Assertion and allows the receiver to validate the requester. This may be useful to determine that the receiver is receiving information it is allowed to hold, based on the requester's identity.
|
|
126
|
+
|
|
127
|
+
The requester identity should be provided by the requester when requesting the data, either via a JMIX API or some other mechanism. The signature model is identical to the sender's.
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
"requester": {
|
|
131
|
+
"name": "Dr. Michael Chen",
|
|
132
|
+
"email": "mchen@university-medical.edu",
|
|
133
|
+
"authentication_level": "directory_verified",
|
|
134
|
+
"assertion": {
|
|
135
|
+
"...": "assertion goes here"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Verification Workflow
|
|
141
|
+
|
|
142
|
+
- Fetch signed_fields and corresponding values from the envelope.
|
|
143
|
+
- Canonicalise the field-value structure.
|
|
144
|
+
- Verify the signature using signing_key.public_key.
|
|
145
|
+
- Optional: Validate directory_attestation.attestation_signature using attestation_public_key.
|
|
146
|
+
- Check expires_at if present.
|
|
147
|
+
|
|
148
|
+
## 5. Modes of Operation
|
|
149
|
+
|
|
150
|
+
| Use Case | Description |
|
|
151
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| Pure P2P | Sender and recipient exchange directly (email, file transfer, etc.). Only envelope metadata is required. |
|
|
153
|
+
| P2P with Aurabox resolution | Recipient or sender uses Aurabox API to discover keys, validate sender IDs, or log consent. |
|
|
154
|
+
| Asymmetric P2P | One user uses Aurabox for discovery, the other operates independently. |
|
|
155
|
+
|
|
156
|
+
## 6. Encryption Workflow
|
|
157
|
+
|
|
158
|
+
- Sender generates a new ephemeral EC keypair.
|
|
159
|
+
- Shared key is derived via ECDH (sender's ephemeral privkey plus recipient's pubkey).
|
|
160
|
+
- Symmetric key is produced using HKDF with SHA-256.
|
|
161
|
+
- Payload is encrypted with AES-256-GCM using the derived key, random IV, and the tag is retained.
|
|
162
|
+
- Envelope is assembled with the encrypted payload and manifest.json.
|
|
163
|
+
|
|
164
|
+
## 7. Recipient Key Management and Discovery
|
|
165
|
+
|
|
166
|
+
JMIX assumes that each recipient maintains a stable, long-term asymmetric keypair (for example, Curve25519) for envelope decryption. The public component of this key must be made available to potential senders through one or more of the following mechanisms:
|
|
167
|
+
|
|
168
|
+
- Out-of-Band Exchange: Recipients may distribute their public keys manually via QR codes, shared config files, or email. This is suitable for small, trusted collaborations.
|
|
169
|
+
- Directory Lookup (optional): Aurabox and compatible directories may expose a public key endpoint, for example, aurabox://org/clinic-a#key-ed25519, to facilitate key discovery and resolution.
|
|
170
|
+
- In-Envelope Reference: Senders may embed a key_reference URI in the sender assertion, indicating the expected recipient key. This does not impact decryption, but helps with auditing and debugging.
|
|
171
|
+
|
|
172
|
+
For secure operation, recipients must safeguard their private key material. Rotation or revocation of keys should be managed via external policy or infrastructure and is not currently defined within the JMIX envelope format.
|
|
173
|
+
|
|
174
|
+
## 8. Deployment Scenarios
|
|
175
|
+
|
|
176
|
+
- Independent Users: Healthcare providers, researchers, or clinicians using custom tooling.
|
|
177
|
+
- Integrated Aurabox Users: Those who opt-in for identity resolution, central consent records, or integration with Aurabox storage and viewing services.
|
|
178
|
+
- Mixed Mode: One peer uses Aurabox to obtain the other’s public key, then sends a self-contained envelope.
|
|
179
|
+
|
|
180
|
+
## 9. Conclusion
|
|
181
|
+
|
|
182
|
+
The JMIX envelope encryption strategy strikes a balance between secure, verifiable data exchange and ecosystem flexibility. It provides robust default behaviour for independent users while allowing powerful enhancements when Aurabox services are available.
|
|
183
|
+
|
|
184
|
+
This ensures imaging data can be safely exchanged, whether between two doctors, hospitals, or platforms, with confidence in its privacy, integrity, and interoperability.
|
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
node: true,
|
|
4
|
+
es2022: true,
|
|
5
|
+
jest: true,
|
|
6
|
+
},
|
|
7
|
+
extends: ['eslint:recommended'],
|
|
8
|
+
parserOptions: {
|
|
9
|
+
ecmaVersion: 2022,
|
|
10
|
+
sourceType: 'module',
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
'no-console': 'warn',
|
|
14
|
+
},
|
|
15
|
+
ignorePatterns: [
|
|
16
|
+
'dist/',
|
|
17
|
+
'coverage/',
|
|
18
|
+
'node_modules/',
|
|
19
|
+
'*.js',
|
|
20
|
+
'**/*.d.ts',
|
|
21
|
+
'src/**/*.ts', // Skip TypeScript files for now
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="WEB_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
8
|
+
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
9
|
+
</content>
|
|
10
|
+
<orderEntry type="inheritedJdk" />
|
|
11
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
12
|
+
</component>
|
|
13
|
+
</module>
|
package/.idea/vcs.xml
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# JMIX TypeScript Library
|
|
2
|
+
|
|
3
|
+
TypeScript implementation of the JMIX (JSON Medical Interchange) format for secure medical data exchange.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- Node.js 20+ LTS
|
|
15
|
+
- npm
|
|
16
|
+
- TypeScript 5+
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { JmixBuilder } from 'jmix-ts';
|
|
22
|
+
|
|
23
|
+
// Create a builder
|
|
24
|
+
const builder = new JmixBuilder({
|
|
25
|
+
outputPath: './tmp'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Create or load configuration
|
|
29
|
+
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
|
|
30
|
+
|
|
31
|
+
// Build envelope from DICOM directory
|
|
32
|
+
const envelope = await builder.buildFromDicom('./samples/study_1', config);
|
|
33
|
+
|
|
34
|
+
// Save to files
|
|
35
|
+
await builder.saveToFiles(envelope, './output');
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Demo
|
|
39
|
+
|
|
40
|
+
Run the demo to see the library in action:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run build
|
|
44
|
+
node demo-no-validation.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This creates a complete JMIX envelope with:
|
|
48
|
+
- `manifest.json` - Security, routing, and patient information
|
|
49
|
+
- `metadata.json` - Clinical data and DICOM metadata
|
|
50
|
+
- `audit.json` - Audit trail and transmission details
|
|
51
|
+
ckaging (folder-based JMIX envelope)
|
|
52
|
+
|
|
53
|
+
Create a proper JMIX folder that includes the original DICOM files under `payload/dicom` and computes a deterministic SHA-256 payload hash stored at `manifest.security.payload_hash`.
|
|
54
|
+
|
|
55
|
+
CLI-like demo:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
node demo-package.js ./samples/study_1 ./tmp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Programmatic API:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { JmixBuilder } from './dist/index.js';
|
|
65
|
+
|
|
66
|
+
const builder = new JmixBuilder();
|
|
67
|
+
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
|
|
68
|
+
const packagePath = await builder.packageToDirectory('./samples/study_1', config, './tmp');
|
|
69
|
+
console.log(packagePath); // => ./tmp/<envelope_id>.JMIX
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Resulting layout:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
<outputRoot>/<envelope_id>.JMIX/
|
|
76
|
+
├── manifest.json
|
|
77
|
+
├── audit.json
|
|
78
|
+
└── payload/
|
|
79
|
+
├── metadata.json
|
|
80
|
+
└── dicom/
|
|
81
|
+
└── ... (original DICOM files, structure preserved)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Encryption (AES-256-GCM with X25519 + HKDF)
|
|
85
|
+
|
|
86
|
+
The library can produce an encrypted JMIX envelope. It tars the plaintext `payload/` directory, encrypts it with AES-256-GCM using a key derived from X25519 ECDH and HKDF-SHA256, then writes `payload.encrypted` and removes the plaintext `payload/`.
|
|
87
|
+
|
|
88
|
+
- Key agreement: Curve25519 (X25519)
|
|
89
|
+
- KDF: HKDF-SHA256 (info = "JMIX-Payload-Encryption")
|
|
90
|
+
- Cipher: AES-256-GCM
|
|
91
|
+
- Manifest fields:
|
|
92
|
+
- `manifest.security.payload_hash` — SHA-256 over the plaintext `payload/` (deterministic, path+newline+bytes per file)
|
|
93
|
+
- `manifest.security.encryption`:
|
|
94
|
+
- `algorithm`: "AES-256-GCM"
|
|
95
|
+
- `ephemeral_public_key`: base64
|
|
96
|
+
- `iv`: base64 (12 bytes)
|
|
97
|
+
- `auth_tag`: base64 (16 bytes)
|
|
98
|
+
|
|
99
|
+
Demo:
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
# recipientPublicKeyBase64 must be a 32-byte Curve25519 key in base64
|
|
103
|
+
node demo-package-encrypted.js <recipientPublicKeyBase64> ./samples/study_1 ./tmp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Programmatic API:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { JmixBuilder } from './dist/index.js';
|
|
110
|
+
|
|
111
|
+
const builder = new JmixBuilder();
|
|
112
|
+
const config = await JmixBuilder.loadConfig('./samples/sample_config.json');
|
|
113
|
+
const packagePath = await builder.packageEncryptedToDirectory(
|
|
114
|
+
'./samples/study_1',
|
|
115
|
+
config,
|
|
116
|
+
'./tmp',
|
|
117
|
+
'<recipientPublicKeyBase64>'
|
|
118
|
+
);
|
|
119
|
+
console.log(packagePath);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Encrypted layout:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
<outputRoot>/<envelope_id>.JMIX/
|
|
126
|
+
├── manifest.json # includes security.payload_hash and security.encryption
|
|
127
|
+
├── audit.json
|
|
128
|
+
└── payload.encrypted # AES-256-GCM encrypted TAR of plaintext payload/
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Decryption
|
|
132
|
+
|
|
133
|
+
You can decrypt an encrypted JMIX envelope and restore a plaintext `payload/` directory. The payload hash is verified against `manifest.security.payload_hash`.
|
|
134
|
+
|
|
135
|
+
Self-contained demo (encrypt + decrypt with a fresh keypair):
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
node demo-decrypt.js ./samples/study_1 ./tmp
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Decrypt an existing folder (requires the matching Curve25519 private key in base64):
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
node demo-decrypt-existing.js ./tmp/<envelope_id>.JMIX <recipientPrivateKeyBase64>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Programmatic API:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { JmixBuilder } from './dist/index.js';
|
|
151
|
+
|
|
152
|
+
const builder = new JmixBuilder();
|
|
153
|
+
const payloadPath = await builder.decryptEnvelope(
|
|
154
|
+
'./tmp/<envelope_id>.JMIX',
|
|
155
|
+
'<recipientPrivateKeyBase64>'
|
|
156
|
+
);
|
|
157
|
+
console.log(payloadPath);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Verify Payload Hash
|
|
161
|
+
|
|
162
|
+
Verify the payload hash listed in `manifest.security.payload_hash`.
|
|
163
|
+
|
|
164
|
+
- Plaintext payload/: compare directly
|
|
165
|
+
- Encrypted payload.encrypted: requires the recipient private key to decrypt to a temp dir before verifying
|
|
166
|
+
|
|
167
|
+
CLI demo:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
# Plaintext envelope
|
|
171
|
+
npm run demo:verify:hash -- ./tmp/<envelope_id>.JMIX
|
|
172
|
+
|
|
173
|
+
# Encrypted envelope (requires private key)
|
|
174
|
+
npm run demo:verify:hash -- ./tmp/<envelope_id>.JMIX <recipientPrivateKeyBase64>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Programmatic API:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { JmixBuilder } from './dist/index.js';
|
|
181
|
+
|
|
182
|
+
const builder = new JmixBuilder();
|
|
183
|
+
const result = await builder.verifyPayloadHash(
|
|
184
|
+
'./tmp/<envelope_id>.JMIX',
|
|
185
|
+
{ recipientPrivateKeyBase64: '<privKey-if-encrypted>' }
|
|
186
|
+
);
|
|
187
|
+
console.log(result.ok, result.expected, result.computed);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Scripts
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Development
|
|
194
|
+
npm run build # Compile TypeScript
|
|
195
|
+
npm run typecheck # Type checking only
|
|
196
|
+
npm test # Run all tests
|
|
197
|
+
npm run test:watch # Run tests in watch mode
|
|
198
|
+
npm run test:coverage # Generate coverage report
|
|
199
|
+
|
|
200
|
+
# Code Quality
|
|
201
|
+
npm run format # Check code formatting
|
|
202
|
+
npm run format:fix # Fix code formatting
|
|
203
|
+
npm run clean # Clean build artifacts
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Architecture
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
src/
|
|
210
|
+
├── JmixBuilder.ts # Main orchestrator
|
|
211
|
+
├── types/index.ts # TypeScript interfaces
|
|
212
|
+
├── validation/ # Ajv schema validation
|
|
213
|
+
├── dicom/ # DICOM file processing
|
|
214
|
+
├── crypto/ # AES-256-GCM encryption (future)
|
|
215
|
+
└── errors/ # Error types
|
|
216
|
+
|
|
217
|
+
tests/ # Jest test suite
|
|
218
|
+
samples/ # Sample JSON files
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Configuration
|
|
222
|
+
|
|
223
|
+
### Schema Validation
|
|
224
|
+
|
|
225
|
+
** This is Alpha quality code and has not yet been fully tested **
|
|
226
|
+
|
|
227
|
+
The library uses configurable schema validation:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const builder = new JmixBuilder({
|
|
231
|
+
schemaValidatorOptions: {
|
|
232
|
+
schemaPath: '../jmix/schemas', // Default: ../jmix/schemas
|
|
233
|
+
strictMode: true // Default: true
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Or via environment variable
|
|
238
|
+
process.env.JMIX_SCHEMA_PATH = '/path/to/schemas';
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Output Directory
|
|
242
|
+
|
|
243
|
+
Follows user rules for temp file output:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
const builder = new JmixBuilder({
|
|
247
|
+
outputPath: './tmp' // Default: ./tmp (not /tmp)
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## DICOM Processing
|
|
252
|
+
|
|
253
|
+
The library automatically:
|
|
254
|
+
|
|
255
|
+
1. Recursively scans directories for DICOM files
|
|
256
|
+
2. Validates files using DICM magic number at byte offset 128
|
|
257
|
+
3. Extracts metadata (patient info, study details, series information)
|
|
258
|
+
4. Falls back gracefully to configuration data when DICOM parsing fails
|
|
259
|
+
5. Supports empty directories for testing
|
|
260
|
+
|
|
261
|
+
## Schema Validation
|
|
262
|
+
|
|
263
|
+
- **Default**: Looks for schemas in `../jmix/schemas`
|
|
264
|
+
- **Configurable**: Override via constructor or environment variable
|
|
265
|
+
- **Graceful Degradation**: Skips validation when schemas not found
|
|
266
|
+
- **Comprehensive**: Validates manifest, metadata, and audit components
|
|
267
|
+
|
|
268
|
+
## Testing
|
|
269
|
+
|
|
270
|
+
Run the comprehensive test suite:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# All tests
|
|
274
|
+
npm test
|
|
275
|
+
|
|
276
|
+
# Specific test files
|
|
277
|
+
npm test JmixBuilder.test.ts
|
|
278
|
+
npm test SchemaValidator.test.ts
|
|
279
|
+
|
|
280
|
+
# With coverage
|
|
281
|
+
npm run test:coverage
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The test suite includes:
|
|
285
|
+
- ✅ JmixBuilder integration tests
|
|
286
|
+
- ✅ Schema validator tests
|
|
287
|
+
- ✅ Type definition tests
|
|
288
|
+
- ✅ DICOM processing tests
|
|
289
|
+
- ✅ Configuration loading tests
|
|
290
|
+
- ✅ File I/O tests
|
|
291
|
+
|
|
292
|
+
All tests use the `/samples` directory for realistic test data and output to `./tmp` for temporary files.
|
|
293
|
+
|
|
294
|
+
## Security Model
|
|
295
|
+
|
|
296
|
+
Implements the JMIX security whitepaper specifications:
|
|
297
|
+
- **AES-256-GCM** encryption with ephemeral public keys
|
|
298
|
+
- **Base64 encoding** for all cryptographic material (ephemeral_public_key, iv, auth_tag)
|
|
299
|
+
- **Forward secrecy** through ephemeral key usage
|
|
300
|
+
- **Optional governance** via Aurabox directory services
|
|
301
|
+
|
|
302
|
+
See `.ai/security.md` for the complete security model.
|
|
303
|
+
|
|
304
|
+
## Development
|
|
305
|
+
|
|
306
|
+
The library follows TypeScript best practices:
|
|
307
|
+
- **ESM modules** with .js imports in source
|
|
308
|
+
- **Strict TypeScript** configuration
|
|
309
|
+
- **Comprehensive type definitions** for all JMIX components
|
|
310
|
+
- **Jest testing** with ts-jest for ESM support
|
|
311
|
+
- **Prettier formatting** for consistent code style
|
|
312
|
+
|
|
313
|
+
## Files Generated
|
|
314
|
+
|
|
315
|
+
A complete JMIX envelope consists of:
|
|
316
|
+
|
|
317
|
+
- **`manifest.json`** - Routing, security classification, patient info, sender/receiver details
|
|
318
|
+
- **`metadata.json`** - Clinical metadata, DICOM study information, custom metadata
|
|
319
|
+
- **`audit.json`** - Audit trail, status, and events timeline
|
|
320
|
+
- **`files.json`** - (Optional) File manifest with hashes and sizes
|
|
321
|
+
|
|
322
|
+
## Compatibility
|
|
323
|
+
|
|
324
|
+
- **JMIX Specification**: Implements JMIX v1.0 format
|
|
325
|
+
- **Schema Compatibility**: Works with JMIX JSON Schema Draft 2020-12
|
|
326
|
+
- **DICOM Support**: Basic DICOM file detection and metadata extraction
|
|
327
|
+
- **Framework Agnostic**: Pure TypeScript, works with any Node.js framework
|
|
328
|
+
|
|
329
|
+
## License
|
|
330
|
+
|
|
331
|
+
Matches the licensing of the JMIX specification and related implementations.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
For detailed API documentation, see the TypeScript declarations in `dist/` after building.
|