json-seal 0.9.1 → 0.10.1
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 +68 -163
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,77 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-

|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-

|
|
7
|
-

|
|
1
|
+
<h1 align="center">json-seal</h1>
|
|
8
2
|
|
|
9
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://github.com/cmyers/json-seal/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI" />
|
|
5
|
+
<img src="https://img.shields.io/npm/v/json-seal" alt="npm version" />
|
|
6
|
+
<img src="https://img.shields.io/badge/Deterministic%20JSON-RFC%208785%20Compliant-success" alt="Deterministic JSON" />
|
|
7
|
+
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="dependencies" />
|
|
8
|
+
<img src="https://img.shields.io/badge/types-TypeScript-blue" alt="types" />
|
|
9
|
+
<img src="https://img.shields.io/bundlephobia/minzip/json-seal" alt="bundle size" />
|
|
10
|
+
<img src="https://img.shields.io/github/license/cmyers/json-seal" alt="license" />
|
|
11
|
+
</p>
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
<h3 align="center">
|
|
14
|
+
A lightweight, zero‑dependency library for creating cryptographically signed, tamper‑proof JSON backups.
|
|
15
|
+
</h3>
|
|
12
16
|
|
|
13
|
-
|
|
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**.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## **Why json‑seal exists**
|
|
24
|
-
|
|
25
|
-
Most security libraries focus on:
|
|
17
|
+
## **Why json‑seal**
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
- authentication tokens (JOSE/JWS/JWT)
|
|
29
|
-
- low‑level primitives (WebCrypto, libsodium)
|
|
19
|
+
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:
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
**“I need to store JSON in a way that guarantees integrity — while keeping it readable, portable, and framework‑agnostic.”**
|
|
32
22
|
|
|
33
|
-
|
|
23
|
+
json‑seal fills that gap. It lets you:
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
- Canonicalize any JSON value
|
|
26
|
+
- Sign it with a private key
|
|
27
|
+
- Embed the public key
|
|
28
|
+
- Verify integrity later
|
|
29
|
+
- Detect any tampering — even a single character
|
|
36
30
|
|
|
37
|
-
It
|
|
31
|
+
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
32
|
|
|
39
33
|
---
|
|
40
34
|
|
|
41
35
|
## **Features**
|
|
42
36
|
|
|
43
|
-
### **
|
|
44
|
-
|
|
45
|
-
If the JSON changes - even whitespace - verification fails.
|
|
37
|
+
### **RFC 8785 Canonical JSON**
|
|
38
|
+
Deterministic, cross‑runtime canonicalization:
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
- sorted keys
|
|
41
|
+
- strict number formatting
|
|
42
|
+
- ECMAScript string escaping
|
|
43
|
+
- duplicate‑key rejection
|
|
44
|
+
- stable UTF‑8 output
|
|
50
45
|
|
|
51
|
-
### **
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
### **RSA‑PSS Signatures**
|
|
47
|
+
Modern asymmetric signing using WebCrypto.
|
|
48
|
+
No shared secrets. No servers. No dependencies.
|
|
54
49
|
|
|
55
|
-
### **
|
|
56
|
-
|
|
50
|
+
### **Portable JSON Backup Format**
|
|
51
|
+
Everything needed for verification is embedded:
|
|
57
52
|
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
- payload
|
|
54
|
+
- timestamp
|
|
55
|
+
- signature
|
|
56
|
+
- public key
|
|
60
57
|
|
|
61
|
-
### **
|
|
62
|
-
|
|
63
|
-
No polyfills, no crypto libraries, no runtime baggage.
|
|
58
|
+
### **Works Everywhere**
|
|
59
|
+
Browsers, PWAs, Node 18+, Bun, Deno, and mobile runtimes.
|
|
64
60
|
|
|
65
|
-
### **
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
- Local storage
|
|
69
|
-
- IndexedDB
|
|
70
|
-
- Sync engines
|
|
71
|
-
- User‑exported backups
|
|
72
|
-
- Cross‑device data portability
|
|
73
|
-
|
|
74
|
-
json‑seal is built for apps that need **trustworthy, tamper‑proof JSON**, not tokens or encrypted blobs.
|
|
61
|
+
### **Zero Dependencies**
|
|
62
|
+
Small, auditable, and safe for long‑term use.
|
|
75
63
|
|
|
76
64
|
---
|
|
77
65
|
|
|
@@ -117,7 +105,7 @@ if (result.valid) {
|
|
|
117
105
|
|
|
118
106
|
---
|
|
119
107
|
|
|
120
|
-
## **
|
|
108
|
+
## **Example Backup**
|
|
121
109
|
|
|
122
110
|
```json
|
|
123
111
|
{
|
|
@@ -132,13 +120,11 @@ if (result.valid) {
|
|
|
132
120
|
}
|
|
133
121
|
```
|
|
134
122
|
|
|
135
|
-
Everything needed to verify the backup is embedded.
|
|
136
|
-
|
|
137
123
|
---
|
|
138
124
|
|
|
139
125
|
## **Tamper Detection**
|
|
140
126
|
|
|
141
|
-
Any modification
|
|
127
|
+
Any modification — even deep inside nested objects — invalidates the signature.
|
|
142
128
|
|
|
143
129
|
```ts
|
|
144
130
|
const tampered = { ...backup, payload: { id: 1, data: "hacked" } };
|
|
@@ -148,123 +134,44 @@ verifyBackup(tampered).valid; // false
|
|
|
148
134
|
|
|
149
135
|
---
|
|
150
136
|
|
|
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
|
-
---
|
|
137
|
+
## **API**
|
|
222
138
|
|
|
223
|
-
###
|
|
139
|
+
### **`generateKeyPair()`**
|
|
140
|
+
Generates a 2048‑bit RSA‑PSS keypair.
|
|
224
141
|
|
|
225
|
-
|
|
142
|
+
### **`signPayload(payload, privateKey, publicKey)`**
|
|
143
|
+
Canonicalizes the payload, signs it, and returns a sealed backup object.
|
|
226
144
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
- synced
|
|
230
|
-
- exported/imported
|
|
145
|
+
### **`verifyBackup(backup)`**
|
|
146
|
+
Verifies the signature and returns `{ valid, payload? }`.
|
|
231
147
|
|
|
232
|
-
|
|
148
|
+
### **`canonicalize(value)`**
|
|
149
|
+
Full RFC 8785 Canonical JSON implementation.
|
|
233
150
|
|
|
234
151
|
---
|
|
235
152
|
|
|
236
|
-
## **
|
|
237
|
-
|
|
238
|
-
### **`generateKeyPair()`**
|
|
239
|
-
Generates a 2048‑bit RSA‑PSS keypair using WebCrypto.
|
|
153
|
+
## **Prior Art**
|
|
240
154
|
|
|
241
|
-
|
|
242
|
-
- Canonicalizes the JSON
|
|
243
|
-
- Signs it using RSA‑PSS SHA‑256
|
|
244
|
-
- Embeds the public key
|
|
245
|
-
- Returns a portable backup object
|
|
155
|
+
json‑seal builds on ideas from:
|
|
246
156
|
|
|
247
|
-
|
|
248
|
-
-
|
|
249
|
-
-
|
|
250
|
-
-
|
|
157
|
+
- **json-canonicalize** — RFC 8785 canonicalization (no signing or backup format)
|
|
158
|
+
- **rfc8785 (Python)** — pure Python canonicalizer
|
|
159
|
+
- **jcs (Elixir)** — Elixir implementation of JCS
|
|
160
|
+
- **JOSE / JWS / JWT** — signing standards focused on tokens, not arbitrary JSON
|
|
251
161
|
|
|
252
|
-
|
|
253
|
-
Deterministic JSON serializer with sorted keys.
|
|
162
|
+
json‑seal combines **canonicalization + signing + verification** into a single, zero‑dependency library designed for **offline‑first, portable JSON integrity**.
|
|
254
163
|
|
|
255
164
|
---
|
|
256
165
|
|
|
257
166
|
## **Testing**
|
|
258
167
|
|
|
259
|
-
|
|
168
|
+
The test suite covers:
|
|
260
169
|
|
|
170
|
+
- RFC 8785 canonicalization
|
|
171
|
+
- Unicode and number edge cases
|
|
261
172
|
- Valid signatures
|
|
262
|
-
- Shallow tampering
|
|
263
|
-
-
|
|
264
|
-
- Missing signature
|
|
265
|
-
- Wrong public key
|
|
266
|
-
- Corrupted signature
|
|
267
|
-
- Canonicalization stability
|
|
173
|
+
- Shallow and deep tampering
|
|
174
|
+
- Missing / wrong / corrupted signatures
|
|
268
175
|
- Large payloads
|
|
269
176
|
- Arrays and primitives
|
|
270
177
|
- RSA‑PSS non‑determinism
|
|
@@ -276,11 +183,9 @@ npm test
|
|
|
276
183
|
```
|
|
277
184
|
|
|
278
185
|
---
|
|
279
|
-
|
|
280
|
-
Pull Requests welcome.
|
|
281
|
-
|
|
186
|
+
Pull Requests are welcome.
|
|
282
187
|
## **License**
|
|
283
188
|
|
|
284
189
|
MIT
|
|
285
190
|
|
|
286
|
-
---
|
|
191
|
+
---
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "json-seal",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|