@ajna-inc/vaults 0.1.3 → 0.1.4
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/README.md +436 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# Post-Quantum Encrypted Vaults
|
|
2
|
+
|
|
3
|
+
Post-quantum encrypted vaults with DIDComm protocol for Credo. Client-side encryption using ML-KEM-768, AES-256-GCM, and Shamir secret sharing with P2P sharing via DIDComm messages.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Vaults module provides secure, client-side encrypted storage with peer-to-peer sharing capabilities. All cryptographic operations happen locally on the agent - no server ever sees plaintext data. It supports multiple encryption suites including post-quantum ML-KEM-768, flexible access policies (passphrase, any-of, all-of, threshold), and optional external storage (S3) for large files.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [How It Works](#how-it-works)
|
|
13
|
+
- [Encryption Suites](#encryption-suites)
|
|
14
|
+
- [Access Policies](#access-policies)
|
|
15
|
+
- [Protocol Flows](#protocol-flows)
|
|
16
|
+
- [Usage Examples](#usage-examples)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Message Types](#message-types)
|
|
19
|
+
- [Event System](#event-system)
|
|
20
|
+
- [Security Considerations](#security-considerations)
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
### Encryption Suites
|
|
25
|
+
- **S3 Suite**: Passphrase-based encryption (Argon2id KDF + AES-256-GCM)
|
|
26
|
+
- **P1 Suite**: Post-quantum encryption (ML-KEM-768 + AES-256-GCM)
|
|
27
|
+
- **H1 Suite**: Hybrid mode (planned)
|
|
28
|
+
|
|
29
|
+
### Access Policies
|
|
30
|
+
- **Passphrase**: Single-user encryption with passphrase-derived key
|
|
31
|
+
- **Any-of**: Multiple recipients, any one can decrypt independently
|
|
32
|
+
- **All-of**: Multiple recipients, all must cooperate to decrypt
|
|
33
|
+
- **Threshold**: Shamir secret sharing with configurable t-of-n threshold
|
|
34
|
+
|
|
35
|
+
### Vault Operations
|
|
36
|
+
- Create, open, update, delete encrypted vaults
|
|
37
|
+
- List vaults and get metadata without decrypting
|
|
38
|
+
- KEM keypair generation and management
|
|
39
|
+
- Document signing vault workflows
|
|
40
|
+
|
|
41
|
+
### DIDComm Protocol
|
|
42
|
+
- 15 message types for P2P vault sharing
|
|
43
|
+
- Request, grant, and deny access to vaults
|
|
44
|
+
- Threshold share request and distribution
|
|
45
|
+
- External storage operator support
|
|
46
|
+
- Problem reporting for error handling
|
|
47
|
+
|
|
48
|
+
### Storage
|
|
49
|
+
- Inline storage in agent wallet
|
|
50
|
+
- External S3 storage for large files
|
|
51
|
+
- Configurable inline/external threshold
|
|
52
|
+
- Storage operator mode
|
|
53
|
+
|
|
54
|
+
## How It Works
|
|
55
|
+
|
|
56
|
+
### Architecture
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Application Code
|
|
60
|
+
|
|
|
61
|
+
VaultsApi
|
|
62
|
+
create / open / update / delete / list
|
|
63
|
+
KEM key mgmt / signing vaults
|
|
64
|
+
|
|
|
65
|
+
+----------------+----------------+
|
|
66
|
+
| | |
|
|
67
|
+
VaultService KEM Service SigningVaultSvc
|
|
68
|
+
| | |
|
|
69
|
+
+--------+-------+-------+--------+
|
|
70
|
+
| |
|
|
71
|
+
EncryptionSvc HPKEService
|
|
72
|
+
| |
|
|
73
|
+
WASM Crypto (30+ functions)
|
|
74
|
+
|
|
|
75
|
+
+----------+----------+
|
|
76
|
+
| | |
|
|
77
|
+
AEAD KEM Shamir
|
|
78
|
+
AES-256 ML-KEM-768 Secret
|
|
79
|
+
GCM Sharing
|
|
80
|
+
| | |
|
|
81
|
+
VaultRepository StorageService
|
|
82
|
+
| |
|
|
83
|
+
Agent Wallet S3 / External
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Encryption Flow
|
|
87
|
+
|
|
88
|
+
1. **Passphrase vault**: Passphrase -> Argon2id KDF -> 256-bit CEK -> AES-256-GCM encrypt
|
|
89
|
+
2. **Post-quantum vault**: ML-KEM-768 encapsulate -> shared secret -> HKDF -> CEK -> AES-256-GCM encrypt
|
|
90
|
+
3. **Threshold vault**: CEK -> Shamir split into n shares -> wrap each share with recipient's KEM public key
|
|
91
|
+
|
|
92
|
+
### Key Commitment
|
|
93
|
+
|
|
94
|
+
Every vault includes a key commitment (`kcmp`) in the header to verify the correct key was used during decryption, preventing key confusion attacks.
|
|
95
|
+
|
|
96
|
+
## Encryption Suites
|
|
97
|
+
|
|
98
|
+
### S3 Suite (Passphrase)
|
|
99
|
+
|
|
100
|
+
**KDF**: Argon2id (configurable memory and iterations)
|
|
101
|
+
**AEAD**: AES-256-GCM
|
|
102
|
+
**Use Case**: Personal vaults protected by a passphrase
|
|
103
|
+
|
|
104
|
+
- Passphrase is stretched via Argon2id with random salt
|
|
105
|
+
- Derived key encrypts data with AES-256-GCM
|
|
106
|
+
- Key commitment ensures correct passphrase verification
|
|
107
|
+
|
|
108
|
+
### P1 Suite (Post-Quantum)
|
|
109
|
+
|
|
110
|
+
**KEM**: ML-KEM-768 (NIST PQC standard)
|
|
111
|
+
**AEAD**: AES-256-GCM
|
|
112
|
+
**Use Case**: Sharing vaults between agents with quantum-resistant security
|
|
113
|
+
|
|
114
|
+
- Recipient's ML-KEM public key encapsulates a shared secret
|
|
115
|
+
- Shared secret is expanded via HKDF to derive CEK
|
|
116
|
+
- CEK encrypts data with AES-256-GCM
|
|
117
|
+
|
|
118
|
+
## Access Policies
|
|
119
|
+
|
|
120
|
+
### Passphrase Policy
|
|
121
|
+
Single user, passphrase-derived encryption key.
|
|
122
|
+
|
|
123
|
+
### Any-of Policy
|
|
124
|
+
Multiple recipients each get an independently-encrypted copy of the CEK wrapped with their KEM public key. Any single recipient can decrypt.
|
|
125
|
+
|
|
126
|
+
### All-of Policy
|
|
127
|
+
All participants must cooperate. The CEK is split such that every participant's contribution is required for reconstruction.
|
|
128
|
+
|
|
129
|
+
### Threshold Policy
|
|
130
|
+
Shamir secret sharing splits the CEK into `n` shares with a threshold of `t`. Any `t` shares can reconstruct the CEK. Each share is wrapped with the respective participant's KEM public key.
|
|
131
|
+
|
|
132
|
+
## Protocol Flows
|
|
133
|
+
|
|
134
|
+
### 1. Create and Open a Vault
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Agent
|
|
138
|
+
|-- create(passphrase, data) --> Argon2id + AES-256-GCM --> VaultRecord stored
|
|
139
|
+
|-- open(passphrase) --> derive key --> AES-256-GCM decrypt --> plaintext
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 2. Share Vault via DIDComm
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Owner Recipient
|
|
146
|
+
| |
|
|
147
|
+
| 1. GrantAccessMessage (header + CT) |
|
|
148
|
+
|--------------------------------------->|
|
|
149
|
+
| |
|
|
150
|
+
| 2. VaultStoredAckMessage |
|
|
151
|
+
|<---------------------------------------|
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### 3. Request Access
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Requester Owner
|
|
158
|
+
| |
|
|
159
|
+
| 1. RequestAccessMessage |
|
|
160
|
+
|--------------------------------------->|
|
|
161
|
+
| |
|
|
162
|
+
| 2. GrantAccessMessage / DenyAccess |
|
|
163
|
+
|<---------------------------------------|
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 4. Threshold Reconstruction
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
Requester ShareHolder1 ShareHolder2
|
|
170
|
+
| | |
|
|
171
|
+
| RequestShareMessage | |
|
|
172
|
+
|----------------------------->| |
|
|
173
|
+
| RequestShareMessage |
|
|
174
|
+
|--------------------------------------------->|
|
|
175
|
+
| | |
|
|
176
|
+
| ProvideShareMessage | |
|
|
177
|
+
|<-----------------------------| |
|
|
178
|
+
| ProvideShareMessage |
|
|
179
|
+
|<---------------------------------------------|
|
|
180
|
+
| |
|
|
181
|
+
| [Reconstruct CEK from t shares] |
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 5. Document Signing Vault
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
Owner Signer
|
|
188
|
+
| |
|
|
189
|
+
| createSigningVault(doc, recipientDid) |
|
|
190
|
+
| shareSigningVault(connectionId) |
|
|
191
|
+
|--------------------------------------->|
|
|
192
|
+
| |
|
|
193
|
+
| openSigningVault()|
|
|
194
|
+
| sign document |
|
|
195
|
+
| returnSignedDocument() |
|
|
196
|
+
|<---------------------------------------|
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Usage Examples
|
|
200
|
+
|
|
201
|
+
### Basic Vault Operations
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { Agent } from '@credo-ts/core'
|
|
205
|
+
import { VaultsModule } from '@ajna-inc/vaults'
|
|
206
|
+
|
|
207
|
+
const agent = new Agent({
|
|
208
|
+
config: { /* ... */ },
|
|
209
|
+
modules: {
|
|
210
|
+
vaults: new VaultsModule()
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
await agent.initialize()
|
|
215
|
+
|
|
216
|
+
// Create a vault
|
|
217
|
+
const vault = await agent.modules.vaults.create({
|
|
218
|
+
passphrase: 'my-secure-passphrase',
|
|
219
|
+
data: Buffer.from('sensitive document content'),
|
|
220
|
+
metadata: {
|
|
221
|
+
description: 'Contract draft',
|
|
222
|
+
tags: ['legal', 'draft']
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Open (decrypt) a vault
|
|
227
|
+
const plaintext = await agent.modules.vaults.open(vault.vaultId, {
|
|
228
|
+
passphrase: 'my-secure-passphrase'
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// List all vaults
|
|
232
|
+
const vaults = await agent.modules.vaults.list()
|
|
233
|
+
|
|
234
|
+
// Get vault info without decrypting
|
|
235
|
+
const info = await agent.modules.vaults.getInfo(vault.vaultId)
|
|
236
|
+
|
|
237
|
+
// Update vault data
|
|
238
|
+
await agent.modules.vaults.update(vault.vaultId, {
|
|
239
|
+
passphrase: 'my-secure-passphrase',
|
|
240
|
+
data: Buffer.from('updated content')
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
// Delete a vault
|
|
244
|
+
await agent.modules.vaults.delete(vault.vaultId)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### KEM Key Management
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// Generate an ML-KEM-768 keypair
|
|
251
|
+
const keypair = await agent.modules.vaults.generateKemKeypair()
|
|
252
|
+
|
|
253
|
+
// Store a peer's KEM public key
|
|
254
|
+
await agent.modules.vaults.storePeerKemKey(connectionId, peerPublicKey)
|
|
255
|
+
|
|
256
|
+
// Retrieve peer's key for encryption
|
|
257
|
+
const peerKey = await agent.modules.vaults.getPeerKemKey(connectionId)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Signing Vault Workflow
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Owner creates a signing vault for a specific recipient
|
|
264
|
+
const signingVault = await agent.modules.vaults.createSigningVault({
|
|
265
|
+
data: documentBytes,
|
|
266
|
+
recipientDid: signerDid
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Owner shares the vault with the signer
|
|
270
|
+
await agent.modules.vaults.shareSigningVault(connectionId, signingVault.vaultId)
|
|
271
|
+
|
|
272
|
+
// Signer opens the vault
|
|
273
|
+
const doc = await agent.modules.vaults.openSigningVault(vaultId)
|
|
274
|
+
|
|
275
|
+
// Signer signs and returns the document
|
|
276
|
+
await agent.modules.vaults.returnSignedDocument(connectionId, {
|
|
277
|
+
vaultId,
|
|
278
|
+
signedData: signedDocBytes
|
|
279
|
+
})
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### S3 Storage Configuration
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const agent = new Agent({
|
|
286
|
+
modules: {
|
|
287
|
+
vaults: new VaultsModule({
|
|
288
|
+
storage: {
|
|
289
|
+
bucket: 'my-vault-bucket',
|
|
290
|
+
region: 'us-east-1',
|
|
291
|
+
accessKeyId: 'AKIA...',
|
|
292
|
+
secretAccessKey: '...'
|
|
293
|
+
},
|
|
294
|
+
inlineThreshold: 1024 * 1024, // 1MB - larger vaults go to S3
|
|
295
|
+
operatorMode: false
|
|
296
|
+
})
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Installation
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
pnpm add @ajna-inc/vaults
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Message Types
|
|
308
|
+
|
|
309
|
+
| Message | Purpose |
|
|
310
|
+
|---------|---------|
|
|
311
|
+
| `CreateVaultMessage` | Notify of vault creation |
|
|
312
|
+
| `UpdateVaultMessage` | Update vault data |
|
|
313
|
+
| `DeleteVaultMessage` | Delete vault |
|
|
314
|
+
| `RequestAccessMessage` | Request access to a vault |
|
|
315
|
+
| `GrantAccessMessage` | Grant access with encrypted data |
|
|
316
|
+
| `DenyAccessMessage` | Deny access request |
|
|
317
|
+
| `RequestShareMessage` | Request a threshold share |
|
|
318
|
+
| `ProvideShareMessage` | Provide a threshold share |
|
|
319
|
+
| `DenyShareMessage` | Deny share request |
|
|
320
|
+
| `StoreVaultMessage` | Store vault at storage service |
|
|
321
|
+
| `VaultStoredAckMessage` | Confirm storage |
|
|
322
|
+
| `RetrieveVaultMessage` | Retrieve vault from storage |
|
|
323
|
+
| `VaultDataMessage` | Vault data transmission |
|
|
324
|
+
| `VaultReferenceMessage` | Storage reference |
|
|
325
|
+
| `VaultProblemReportMessage` | Error reporting |
|
|
326
|
+
|
|
327
|
+
## Event System
|
|
328
|
+
|
|
329
|
+
Subscribe to vault lifecycle events:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { VaultEventTypes } from '@ajna-inc/vaults'
|
|
333
|
+
|
|
334
|
+
agent.events.on(VaultEventTypes.VaultCreated, (event) => {
|
|
335
|
+
console.log('Vault created:', event.payload.vaultId)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
agent.events.on(VaultEventTypes.AccessGranted, (event) => {
|
|
339
|
+
console.log('Access granted to vault:', event.payload.vaultId)
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
agent.events.on(VaultEventTypes.ThresholdMet, (event) => {
|
|
343
|
+
console.log('Threshold met, vault can be decrypted')
|
|
344
|
+
})
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Available Events
|
|
348
|
+
|
|
349
|
+
- `VaultCreated`, `VaultOpened`, `VaultUpdated`, `VaultDeleted`
|
|
350
|
+
- `VaultShared`, `VaultError`
|
|
351
|
+
- `AccessRequested`, `AccessGranted`, `AccessDenied`
|
|
352
|
+
- `ThresholdMet`, `ShareProvided`, `ShareRequested`, `ShareDenied`
|
|
353
|
+
- `StorageAllocated`, `StorageUploaded`, `StorageDownloaded`
|
|
354
|
+
|
|
355
|
+
## Security Considerations
|
|
356
|
+
|
|
357
|
+
### Cryptographic Primitives
|
|
358
|
+
- **ML-KEM-768**: NIST post-quantum standard for key encapsulation
|
|
359
|
+
- **AES-256-GCM**: Authenticated encryption with 256-bit keys
|
|
360
|
+
- **Argon2id**: Memory-hard KDF resistant to GPU/ASIC attacks
|
|
361
|
+
- **Shamir Secret Sharing**: Information-theoretically secure threshold scheme
|
|
362
|
+
- **Key Commitment**: Prevents key confusion and related attacks
|
|
363
|
+
|
|
364
|
+
### Best Practices
|
|
365
|
+
1. Use strong passphrases for passphrase-based vaults
|
|
366
|
+
2. Configure adequate Argon2id memory (default is reasonable for most use cases)
|
|
367
|
+
3. Store KEM private keys securely in the agent wallet
|
|
368
|
+
4. Verify key commitments are checked on every decryption
|
|
369
|
+
5. Use threshold policies for high-value data requiring multi-party authorization
|
|
370
|
+
6. Consider external storage for large files to keep wallet lean
|
|
371
|
+
|
|
372
|
+
### Client-Side Only
|
|
373
|
+
All encryption and decryption happens locally on the agent. No server, mediator, or storage operator ever has access to plaintext data or encryption keys.
|
|
374
|
+
|
|
375
|
+
## Vault Header Format
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
{
|
|
379
|
+
v: 1, // Protocol version
|
|
380
|
+
suite: 'S3' | 'P1' | 'H1', // Encryption suite
|
|
381
|
+
aead: 'AES-256-GCM', // AEAD algorithm
|
|
382
|
+
docId: string, // Document identifier
|
|
383
|
+
vaultId: string, // Vault identifier
|
|
384
|
+
epoch: number, // Version counter
|
|
385
|
+
nonce: string, // base64url nonce
|
|
386
|
+
kcmp: string, // Key commitment
|
|
387
|
+
salt?: string, // Argon2id salt (S3 suite)
|
|
388
|
+
argon2?: { // KDF parameters (S3 suite)
|
|
389
|
+
memory: number,
|
|
390
|
+
iterations: number
|
|
391
|
+
},
|
|
392
|
+
policy?: { // Access policy
|
|
393
|
+
mode: 'passphrase' | 'any-of' | 'all-of' | 'threshold'
|
|
394
|
+
},
|
|
395
|
+
recipients?: RecipientWrap[], // Per-recipient wrapped keys
|
|
396
|
+
shares?: ThresholdShare[], // Threshold shares
|
|
397
|
+
metadata?: { // Optional metadata
|
|
398
|
+
description?: string,
|
|
399
|
+
tags?: string[]
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Error Handling
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { VaultError, BadSuiteError, DecryptKemError, DecryptAeadError, PolicyError } from '@ajna-inc/vaults'
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await agent.modules.vaults.open(vaultId, { passphrase: 'wrong' })
|
|
411
|
+
} catch (error) {
|
|
412
|
+
if (error instanceof DecryptAeadError) {
|
|
413
|
+
console.log('Wrong passphrase or corrupted data')
|
|
414
|
+
} else if (error instanceof DecryptKemError) {
|
|
415
|
+
console.log('KEM decapsulation failed - wrong key')
|
|
416
|
+
} else if (error instanceof PolicyError) {
|
|
417
|
+
console.log('Access policy violation')
|
|
418
|
+
} else if (error instanceof BadSuiteError) {
|
|
419
|
+
console.log('Unknown encryption suite')
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Testing
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
# Run vault tests
|
|
428
|
+
pnpm test
|
|
429
|
+
|
|
430
|
+
# Watch mode
|
|
431
|
+
pnpm test:watch
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## License
|
|
435
|
+
|
|
436
|
+
Apache-2.0
|