json-seal 0.9.0 → 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 +63 -155
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,71 +1,62 @@
|
|
|
1
1
|
# **json‑seal**
|
|
2
|
+

|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

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