json-seal 0.9.1 → 0.10.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/README.md +59 -157
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,77 +1,62 @@
|
|
|
1
1
|
# **json‑seal**
|
|
2
2
|

|
|
3
|
-

|
|
4
|
+

|
|
5
|
+

|
|
4
6
|

|
|
5
7
|

|
|
6
|
-

|
|
7
8
|

|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
json‑seal lets you:
|
|
12
|
-
|
|
13
|
-
- Canonicalize any JSON object
|
|
14
|
-
- Sign it with a private key
|
|
15
|
-
- Embed the public key
|
|
16
|
-
- Verify integrity later
|
|
17
|
-
- Detect any tampering - even a single character
|
|
18
|
-
|
|
19
|
-
It’s like JWS, but for **arbitrary JSON documents**, without JWT complexity, and designed for **offline‑first apps**, **local backups**, and **portable integrity checks**.
|
|
10
|
+
**A lightweight, zero‑dependency library for creating cryptographically signed, tamper‑proof JSON backups.**
|
|
20
11
|
|
|
21
12
|
---
|
|
22
13
|
|
|
23
|
-
## **Why json‑seal
|
|
14
|
+
## **Why json‑seal**
|
|
24
15
|
|
|
25
|
-
Most security libraries focus on:
|
|
16
|
+
Apps often need to store or transmit JSON in a way that guarantees it hasn’t been tampered with — without relying on servers, tokens, or opaque binary formats. Most security libraries focus on encrypted blobs, authentication tokens, or low‑level crypto primitives, but none solve the simple problem:
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
- authentication tokens (JOSE/JWS/JWT)
|
|
29
|
-
- low‑level primitives (WebCrypto, libsodium)
|
|
18
|
+
**“I need to store JSON in a way that guarantees integrity — while keeping it readable, portable, and framework‑agnostic.”**
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
json‑seal fills that gap. It lets you:
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
- Canonicalize any JSON value
|
|
23
|
+
- Sign it with a private key
|
|
24
|
+
- Embed the public key
|
|
25
|
+
- Verify integrity later
|
|
26
|
+
- Detect any tampering — even a single character
|
|
36
27
|
|
|
37
|
-
It
|
|
28
|
+
It’s like JWS, but for **arbitrary JSON documents**, without JWT complexity, and designed for **offline‑first apps**, **local backups**, and **portable integrity checks**. It turns any JSON value into a **portable, human‑readable, cryptographically signed artifact** that can be verified anywhere, on any device, with no external dependencies.
|
|
38
29
|
|
|
39
30
|
---
|
|
40
31
|
|
|
41
32
|
## **Features**
|
|
42
33
|
|
|
43
|
-
### **
|
|
44
|
-
|
|
45
|
-
If the JSON changes - even whitespace - verification fails.
|
|
34
|
+
### **RFC 8785 Canonical JSON**
|
|
35
|
+
Deterministic, cross‑runtime canonicalization:
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
- sorted keys
|
|
38
|
+
- strict number formatting
|
|
39
|
+
- ECMAScript string escaping
|
|
40
|
+
- duplicate‑key rejection
|
|
41
|
+
- stable UTF‑8 output
|
|
50
42
|
|
|
51
|
-
### **
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
### **RSA‑PSS Signatures**
|
|
44
|
+
Modern asymmetric signing using WebCrypto.
|
|
45
|
+
No shared secrets. No servers. No dependencies.
|
|
54
46
|
|
|
55
|
-
### **
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
### **Framework‑agnostic**
|
|
59
|
-
Angular, React, Vue, Svelte, Ionic, Capacitor, PWAs, Node, Bun, Deno - json‑seal fits everywhere.
|
|
60
|
-
|
|
61
|
-
### **Zero dependencies**
|
|
62
|
-
Small, auditable, and safe for long‑term use.
|
|
63
|
-
No polyfills, no crypto libraries, no runtime baggage.
|
|
47
|
+
### **Portable JSON Backup Format**
|
|
48
|
+
Everything needed for verification is embedded:
|
|
64
49
|
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
- payload
|
|
51
|
+
- timestamp
|
|
52
|
+
- signature
|
|
53
|
+
- public key
|
|
67
54
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- Sync engines
|
|
71
|
-
- User‑exported backups
|
|
72
|
-
- Cross‑device data portability
|
|
55
|
+
### **Works Everywhere**
|
|
56
|
+
Browsers, PWAs, Node 18+, Bun, Deno, and mobile runtimes.
|
|
73
57
|
|
|
74
|
-
|
|
58
|
+
### **Zero Dependencies**
|
|
59
|
+
Small, auditable, and safe for long‑term use.
|
|
75
60
|
|
|
76
61
|
---
|
|
77
62
|
|
|
@@ -117,7 +102,7 @@ if (result.valid) {
|
|
|
117
102
|
|
|
118
103
|
---
|
|
119
104
|
|
|
120
|
-
## **
|
|
105
|
+
## **Example Backup**
|
|
121
106
|
|
|
122
107
|
```json
|
|
123
108
|
{
|
|
@@ -132,13 +117,11 @@ if (result.valid) {
|
|
|
132
117
|
}
|
|
133
118
|
```
|
|
134
119
|
|
|
135
|
-
Everything needed to verify the backup is embedded.
|
|
136
|
-
|
|
137
120
|
---
|
|
138
121
|
|
|
139
122
|
## **Tamper Detection**
|
|
140
123
|
|
|
141
|
-
Any modification
|
|
124
|
+
Any modification — even deep inside nested objects — invalidates the signature.
|
|
142
125
|
|
|
143
126
|
```ts
|
|
144
127
|
const tampered = { ...backup, payload: { id: 1, data: "hacked" } };
|
|
@@ -148,123 +131,44 @@ verifyBackup(tampered).valid; // false
|
|
|
148
131
|
|
|
149
132
|
---
|
|
150
133
|
|
|
151
|
-
## **
|
|
152
|
-
|
|
153
|
-
`generateKeyPair()` should be called **once**, not on every backup.
|
|
154
|
-
Apps are expected to generate or receive a keypair during onboarding and store it securely.
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
### **App‑generated keys**
|
|
159
|
-
|
|
160
|
-
Most offline‑first apps generate a keypair on first launch:
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
import { generateKeyPair } from "json-seal";
|
|
164
|
-
|
|
165
|
-
const { privateKey, publicKey } = await generateKeyPair();
|
|
166
|
-
|
|
167
|
-
secureStore.set("privateKey", privateKey);
|
|
168
|
-
secureStore.set("publicKey", publicKey);
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
On subsequent runs:
|
|
172
|
-
|
|
173
|
-
```ts
|
|
174
|
-
const privateKey = secureStore.get("privateKey");
|
|
175
|
-
const publicKey = secureStore.get("publicKey");
|
|
176
|
-
|
|
177
|
-
const backup = await signPayload(data, privateKey, publicKey);
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
---
|
|
181
|
-
|
|
182
|
-
### **Where to store keys**
|
|
183
|
-
|
|
184
|
-
Storage depends on the platform:
|
|
185
|
-
|
|
186
|
-
- iOS → Keychain
|
|
187
|
-
- Android → Keystore
|
|
188
|
-
- Web → IndexedDB + WebCrypto
|
|
189
|
-
- Desktop → OS keyring or encrypted local file
|
|
190
|
-
- Node → environment variables or encrypted file
|
|
191
|
-
|
|
192
|
-
json‑seal intentionally does **not** handle storage so it can remain environment‑agnostic.
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
### **Server‑generated keys**
|
|
197
|
-
|
|
198
|
-
Some architectures prefer the backend to generate and manage keys:
|
|
199
|
-
|
|
200
|
-
1. Server generates keypair
|
|
201
|
-
2. Server stores private key
|
|
202
|
-
3. Server sends public key to the app
|
|
203
|
-
4. App signs backups using the server’s public key
|
|
204
|
-
5. Server verifies integrity later
|
|
205
|
-
|
|
206
|
-
Useful for multi‑device accounts or enterprise systems.
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### **Key rotation**
|
|
211
|
-
|
|
212
|
-
json‑seal embeds the public key inside each backup, so old backups remain verifiable even after rotation.
|
|
213
|
-
|
|
214
|
-
Typical strategy:
|
|
215
|
-
|
|
216
|
-
- generate a new keypair yearly
|
|
217
|
-
- store the new private key
|
|
218
|
-
- keep old public keys for verification
|
|
219
|
-
- continue verifying old backups without breaking anything
|
|
220
|
-
|
|
221
|
-
---
|
|
134
|
+
## **API**
|
|
222
135
|
|
|
223
|
-
###
|
|
136
|
+
### **`generateKeyPair()`**
|
|
137
|
+
Generates a 2048‑bit RSA‑PSS keypair.
|
|
224
138
|
|
|
225
|
-
|
|
139
|
+
### **`signPayload(payload, privateKey, publicKey)`**
|
|
140
|
+
Canonicalizes the payload, signs it, and returns a sealed backup object.
|
|
226
141
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
- synced
|
|
230
|
-
- exported/imported
|
|
142
|
+
### **`verifyBackup(backup)`**
|
|
143
|
+
Verifies the signature and returns `{ valid, payload? }`.
|
|
231
144
|
|
|
232
|
-
|
|
145
|
+
### **`canonicalize(value)`**
|
|
146
|
+
Full RFC 8785 Canonical JSON implementation.
|
|
233
147
|
|
|
234
148
|
---
|
|
235
149
|
|
|
236
|
-
## **
|
|
237
|
-
|
|
238
|
-
### **`generateKeyPair()`**
|
|
239
|
-
Generates a 2048‑bit RSA‑PSS keypair using WebCrypto.
|
|
150
|
+
## **Prior Art**
|
|
240
151
|
|
|
241
|
-
|
|
242
|
-
- Canonicalizes the JSON
|
|
243
|
-
- Signs it using RSA‑PSS SHA‑256
|
|
244
|
-
- Embeds the public key
|
|
245
|
-
- Returns a portable backup object
|
|
152
|
+
json‑seal builds on ideas from:
|
|
246
153
|
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
154
|
+
- **json-canonicalize** — RFC 8785 canonicalization (no signing or backup format)
|
|
155
|
+
- **rfc8785 (Python)** — pure Python canonicalizer
|
|
156
|
+
- **jcs (Elixir)** — Elixir implementation of JCS
|
|
157
|
+
- **JOSE / JWS / JWT** — signing standards focused on tokens, not arbitrary JSON
|
|
251
158
|
|
|
252
|
-
|
|
253
|
-
Deterministic JSON serializer with sorted keys.
|
|
159
|
+
json‑seal combines **canonicalization + signing + verification** into a single, zero‑dependency library designed for **offline‑first, portable JSON integrity**.
|
|
254
160
|
|
|
255
161
|
---
|
|
256
162
|
|
|
257
163
|
## **Testing**
|
|
258
164
|
|
|
259
|
-
|
|
165
|
+
The test suite covers:
|
|
260
166
|
|
|
167
|
+
- RFC 8785 canonicalization
|
|
168
|
+
- Unicode and number edge cases
|
|
261
169
|
- Valid signatures
|
|
262
|
-
- Shallow tampering
|
|
263
|
-
-
|
|
264
|
-
- Missing signature
|
|
265
|
-
- Wrong public key
|
|
266
|
-
- Corrupted signature
|
|
267
|
-
- Canonicalization stability
|
|
170
|
+
- Shallow and deep tampering
|
|
171
|
+
- Missing / wrong / corrupted signatures
|
|
268
172
|
- Large payloads
|
|
269
173
|
- Arrays and primitives
|
|
270
174
|
- RSA‑PSS non‑determinism
|
|
@@ -276,11 +180,9 @@ npm test
|
|
|
276
180
|
```
|
|
277
181
|
|
|
278
182
|
---
|
|
279
|
-
|
|
280
|
-
Pull Requests welcome.
|
|
281
|
-
|
|
183
|
+
Pull Requests are welcome.
|
|
282
184
|
## **License**
|
|
283
185
|
|
|
284
186
|
MIT
|
|
285
187
|
|
|
286
|
-
---
|
|
188
|
+
---
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-seal",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Create cryptographically signed, tamper‑proof JSON backups with zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|