@nuggetslife/vc 0.0.21 → 0.0.23
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/index.d.ts +114 -0
- package/index.js +16 -1
- package/package.json +8 -11
- package/src/bls_signatures/bbs_bls_holder_bound_signature_2022/mod.rs +7 -38
- package/src/bls_signatures/bbs_bls_holder_bound_signature_proof_2022/mod.rs +3 -13
- package/src/bls_signatures/bbs_bls_signature_2020/mod.rs +4 -34
- package/src/bls_signatures/bbs_bls_signature_2020/types.rs +0 -1
- package/src/bls_signatures/bbs_bls_signature_proof_2020/mod.rs +3 -13
- package/src/bls_signatures/bls_12381_g2_keypair/mod.rs +16 -16
- package/src/bls_signatures/bound_bls_12381_g2_keypair/mod.rs +3 -3
- package/src/jose.rs +415 -0
- package/src/jsonld.rs +4 -26
- package/src/ld_signatures.rs +29 -24
- package/src/lib.rs +1 -0
- package/test.mjs +38 -0
- package/test_fuzz.mjs +202 -0
- package/test_jose.mjs +497 -0
- package/test_jsonld_crossverify.mjs +1 -1
package/test_fuzz.mjs
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { strict as assert } from 'assert';
|
|
2
|
+
|
|
3
|
+
// Import NAPI bindings - matching patterns from test.mjs and test_jose.mjs
|
|
4
|
+
import {
|
|
5
|
+
JsonLd,
|
|
6
|
+
BbsBlsSignature2020,
|
|
7
|
+
BbsBlsSignatureProof2020,
|
|
8
|
+
Bls12381G2KeyPair,
|
|
9
|
+
generateJwk,
|
|
10
|
+
generateKeyPair,
|
|
11
|
+
generalEncryptJson,
|
|
12
|
+
decryptJson,
|
|
13
|
+
compactSignJson,
|
|
14
|
+
compactJsonVerify,
|
|
15
|
+
flattenedSignJson,
|
|
16
|
+
jsonVerify,
|
|
17
|
+
JoseNamedCurve,
|
|
18
|
+
JoseKeyEncryption,
|
|
19
|
+
JoseContentEncryption,
|
|
20
|
+
JoseSigningAlgorithm,
|
|
21
|
+
} from './index.js';
|
|
22
|
+
|
|
23
|
+
let passed = 0;
|
|
24
|
+
let failed = 0;
|
|
25
|
+
|
|
26
|
+
async function test(name, fn) {
|
|
27
|
+
try {
|
|
28
|
+
await fn();
|
|
29
|
+
passed++;
|
|
30
|
+
console.log(` PASS: ${name}`);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
failed++;
|
|
33
|
+
console.log(` FAIL: ${name}`);
|
|
34
|
+
console.log(` ${e.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function randomJson(depth = 0) {
|
|
39
|
+
if (depth > 3) return Math.random().toString(36);
|
|
40
|
+
const type = Math.floor(Math.random() * 5);
|
|
41
|
+
switch (type) {
|
|
42
|
+
case 0: return null;
|
|
43
|
+
case 1: return Math.random() * 1000 - 500;
|
|
44
|
+
case 2: return Math.random().toString(36).substring(2);
|
|
45
|
+
case 3: {
|
|
46
|
+
const arr = [];
|
|
47
|
+
for (let i = 0; i < Math.floor(Math.random() * 4); i++) arr.push(randomJson(depth + 1));
|
|
48
|
+
return arr;
|
|
49
|
+
}
|
|
50
|
+
case 4: {
|
|
51
|
+
const obj = {};
|
|
52
|
+
for (let i = 0; i < Math.floor(Math.random() * 4); i++) {
|
|
53
|
+
obj[Math.random().toString(36).substring(2, 7)] = randomJson(depth + 1);
|
|
54
|
+
}
|
|
55
|
+
return obj;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log('\n=== NAPI Fuzz Tests ===\n');
|
|
61
|
+
|
|
62
|
+
// Category 1: Random JSON to JsonLd operations (should not crash)
|
|
63
|
+
console.log('--- JSON-LD with random inputs ---');
|
|
64
|
+
for (let i = 0; i < 20; i++) {
|
|
65
|
+
await test(`JsonLd.expand random input #${i}`, async () => {
|
|
66
|
+
const jsonld = new JsonLd();
|
|
67
|
+
try {
|
|
68
|
+
await jsonld.expand(randomJson());
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Errors are fine, crashes are not
|
|
71
|
+
assert.ok(e instanceof Error, 'should throw Error, not crash');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < 10; i++) {
|
|
77
|
+
await test(`JsonLd.compact random input #${i}`, async () => {
|
|
78
|
+
const jsonld = new JsonLd();
|
|
79
|
+
try {
|
|
80
|
+
await jsonld.compact(randomJson(), randomJson());
|
|
81
|
+
} catch (e) {
|
|
82
|
+
assert.ok(e instanceof Error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < 10; i++) {
|
|
88
|
+
await test(`JsonLd.frame random input #${i}`, async () => {
|
|
89
|
+
const jsonld = new JsonLd();
|
|
90
|
+
try {
|
|
91
|
+
await jsonld.frame(randomJson(), randomJson());
|
|
92
|
+
} catch (e) {
|
|
93
|
+
assert.ok(e instanceof Error);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Category 2: Invalid key pairs to BBS operations
|
|
99
|
+
console.log('\n--- BBS with invalid inputs ---');
|
|
100
|
+
|
|
101
|
+
await test('BbsBlsSignature2020.createProof with no key (verifier-only)', async () => {
|
|
102
|
+
try {
|
|
103
|
+
const suite = new BbsBlsSignature2020();
|
|
104
|
+
await suite.createProof({ document: {}, contexts: {} });
|
|
105
|
+
} catch (e) {
|
|
106
|
+
assert.ok(e instanceof Error);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await test('BbsBlsSignature2020.verifyProof with random proof', async () => {
|
|
111
|
+
try {
|
|
112
|
+
const suite = new BbsBlsSignature2020();
|
|
113
|
+
await suite.verifyProof({ document: randomJson(), contexts: {} });
|
|
114
|
+
} catch (e) {
|
|
115
|
+
assert.ok(e instanceof Error);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Category 3: Empty/null/undefined arguments
|
|
120
|
+
console.log('\n--- Null/undefined arguments ---');
|
|
121
|
+
|
|
122
|
+
await test('JsonLd.expand with null', async () => {
|
|
123
|
+
const jsonld = new JsonLd();
|
|
124
|
+
try { await jsonld.expand(null); } catch (e) { assert.ok(e instanceof Error); }
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await test('JsonLd.expand with undefined', async () => {
|
|
128
|
+
const jsonld = new JsonLd();
|
|
129
|
+
try { await jsonld.expand(undefined); } catch (e) { assert.ok(e instanceof Error); }
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await test('JsonLd.compact with null context', async () => {
|
|
133
|
+
const jsonld = new JsonLd();
|
|
134
|
+
try { await jsonld.compact({}, null); } catch (e) { assert.ok(e instanceof Error); }
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await test('generateJwk with invalid enum value', async () => {
|
|
138
|
+
try { generateJwk(999); } catch (e) { assert.ok(e instanceof Error); }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Category 4: JOSE with random inputs
|
|
142
|
+
console.log('\n--- JOSE with random inputs ---');
|
|
143
|
+
|
|
144
|
+
await test('compactSignJson with random payload', async () => {
|
|
145
|
+
try {
|
|
146
|
+
const key = generateJwk(JoseNamedCurve.P256);
|
|
147
|
+
compactSignJson(JoseSigningAlgorithm.Es256, randomJson(), key);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
assert.ok(e instanceof Error);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await test('compactJsonVerify with random JWS string', async () => {
|
|
154
|
+
try {
|
|
155
|
+
const key = generateJwk(JoseNamedCurve.P256);
|
|
156
|
+
compactJsonVerify('not.a.jws', key);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
assert.ok(e instanceof Error);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await test('decryptJson with random JWE', async () => {
|
|
163
|
+
try {
|
|
164
|
+
const key = generateJwk(JoseNamedCurve.X25519);
|
|
165
|
+
decryptJson(randomJson(), key);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
assert.ok(e instanceof Error);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Category 5: Concurrent calls
|
|
172
|
+
console.log('\n--- Concurrent operations ---');
|
|
173
|
+
|
|
174
|
+
await test('50 concurrent JsonLd.expand calls', async () => {
|
|
175
|
+
const jsonld = new JsonLd();
|
|
176
|
+
const doc = {"@context": {"name": "http://schema.org/name"}, "name": "test"};
|
|
177
|
+
const promises = Array.from({length: 50}, () => jsonld.expand(doc).catch(() => null));
|
|
178
|
+
const results = await Promise.all(promises);
|
|
179
|
+
// All should succeed or gracefully fail
|
|
180
|
+
assert.ok(results.length === 50);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await test('50 concurrent JOSE sign operations', async () => {
|
|
184
|
+
const key = generateJwk(JoseNamedCurve.P256);
|
|
185
|
+
const payload = {sub: "test", iat: Date.now()};
|
|
186
|
+
const results = [];
|
|
187
|
+
for (let i = 0; i < 50; i++) {
|
|
188
|
+
try {
|
|
189
|
+
results.push(compactSignJson(JoseSigningAlgorithm.Es256, payload, key));
|
|
190
|
+
} catch (e) {
|
|
191
|
+
results.push(null);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
assert.ok(results.length === 50);
|
|
195
|
+
// All should have succeeded
|
|
196
|
+
const successes = results.filter(r => r !== null);
|
|
197
|
+
assert.ok(successes.length === 50, `Expected 50 successes, got ${successes.length}`);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Summary
|
|
201
|
+
console.log(`\n=== Results: ${passed} passed, ${failed} failed out of ${passed + failed} ===`);
|
|
202
|
+
if (failed > 0) process.exit(1);
|
package/test_jose.mjs
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import {
|
|
5
|
+
JoseNamedCurve,
|
|
6
|
+
JoseContentEncryption,
|
|
7
|
+
JoseKeyEncryption,
|
|
8
|
+
JoseSigningAlgorithm,
|
|
9
|
+
generateJwk,
|
|
10
|
+
generateKeyPair,
|
|
11
|
+
joseEncrypt,
|
|
12
|
+
joseDecrypt,
|
|
13
|
+
generalEncryptJson,
|
|
14
|
+
decryptJson,
|
|
15
|
+
compactSignJson,
|
|
16
|
+
compactJsonVerify,
|
|
17
|
+
flattenedSignJson,
|
|
18
|
+
jsonVerify,
|
|
19
|
+
generalSignJson,
|
|
20
|
+
} from './index.js';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
// =====================================================================
|
|
24
|
+
// Key Generation — Task 15
|
|
25
|
+
// =====================================================================
|
|
26
|
+
|
|
27
|
+
test('generateJwk with P-256 returns EC JWK', () => {
|
|
28
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
29
|
+
assert.ok(jwk, 'generateJwk returned a result');
|
|
30
|
+
assert.equal(jwk.kty, 'EC', 'key type is EC');
|
|
31
|
+
assert.equal(jwk.crv, 'P-256', 'curve is P-256');
|
|
32
|
+
assert.ok(jwk.x, 'has x coordinate');
|
|
33
|
+
assert.ok(jwk.y, 'has y coordinate');
|
|
34
|
+
assert.ok(jwk.d, 'has private key (d)');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('generateJwk with P-384 returns EC JWK', () => {
|
|
38
|
+
const jwk = generateJwk(JoseNamedCurve.P384);
|
|
39
|
+
assert.equal(jwk.kty, 'EC');
|
|
40
|
+
assert.equal(jwk.crv, 'P-384');
|
|
41
|
+
assert.ok(jwk.d, 'has private key');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('generateJwk with P-521 returns EC JWK', () => {
|
|
45
|
+
const jwk = generateJwk(JoseNamedCurve.P521);
|
|
46
|
+
assert.equal(jwk.kty, 'EC');
|
|
47
|
+
assert.equal(jwk.crv, 'P-521');
|
|
48
|
+
assert.ok(jwk.x, 'has x coordinate');
|
|
49
|
+
assert.ok(jwk.y, 'has y coordinate');
|
|
50
|
+
assert.ok(jwk.d, 'has private key');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('compactSignJson + compactJsonVerify with ES512 (P-521)', () => {
|
|
54
|
+
const jwk = generateJwk(JoseNamedCurve.P521);
|
|
55
|
+
const payload = { alg: 'ES512 test', curve: 'P-521' };
|
|
56
|
+
|
|
57
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Es512, payload, jwk);
|
|
58
|
+
const verified = compactJsonVerify(jws, jwk);
|
|
59
|
+
assert.deepStrictEqual(verified, payload);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('generateJwk with Ed25519 returns OKP JWK', () => {
|
|
63
|
+
const jwk = generateJwk(JoseNamedCurve.Ed25519);
|
|
64
|
+
assert.equal(jwk.kty, 'OKP', 'key type is OKP');
|
|
65
|
+
assert.equal(jwk.crv, 'Ed25519', 'curve is Ed25519');
|
|
66
|
+
assert.ok(jwk.x, 'has public key (x)');
|
|
67
|
+
assert.ok(jwk.d, 'has private key (d)');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('generateJwk with X25519 returns OKP JWK', () => {
|
|
71
|
+
const jwk = generateJwk(JoseNamedCurve.X25519);
|
|
72
|
+
assert.equal(jwk.kty, 'OKP');
|
|
73
|
+
assert.equal(jwk.crv, 'X25519');
|
|
74
|
+
assert.ok(jwk.x, 'has public key');
|
|
75
|
+
assert.ok(jwk.d, 'has private key');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('generateKeyPair with P-256 returns full key pair', () => {
|
|
79
|
+
const kp = generateKeyPair(JoseNamedCurve.P256);
|
|
80
|
+
assert.ok(kp, 'generateKeyPair returned a result');
|
|
81
|
+
assert.ok(kp.jwk_key_pair, 'has jwk_key_pair');
|
|
82
|
+
assert.ok(kp.jwk_private_key, 'has jwk_private_key');
|
|
83
|
+
assert.ok(kp.jwk_public_key, 'has jwk_public_key');
|
|
84
|
+
assert.ok(kp.pem_private_key, 'has pem_private_key');
|
|
85
|
+
assert.ok(kp.pem_public_key, 'has pem_public_key');
|
|
86
|
+
assert.ok(kp.der_private_key, 'has der_private_key');
|
|
87
|
+
assert.ok(kp.der_public_key, 'has der_public_key');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('generateKeyPair with Ed25519 returns full key pair', () => {
|
|
91
|
+
const kp = generateKeyPair(JoseNamedCurve.Ed25519);
|
|
92
|
+
assert.ok(kp.jwk_key_pair, 'has jwk_key_pair');
|
|
93
|
+
assert.ok(kp.jwk_public_key, 'has jwk_public_key');
|
|
94
|
+
assert.ok(kp.pem_private_key, 'has pem_private_key');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('generateJwk generates unique keys each time', () => {
|
|
98
|
+
const jwk1 = generateJwk(JoseNamedCurve.P256);
|
|
99
|
+
const jwk2 = generateJwk(JoseNamedCurve.P256);
|
|
100
|
+
assert.notEqual(jwk1.d, jwk2.d, 'private keys should differ');
|
|
101
|
+
assert.notEqual(jwk1.x, jwk2.x, 'public keys should differ');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('generateKeyPair public key has no private component', () => {
|
|
105
|
+
const kp = generateKeyPair(JoseNamedCurve.P256);
|
|
106
|
+
assert.ok(!kp.jwk_public_key.d, 'public JWK should not have d');
|
|
107
|
+
assert.ok(kp.jwk_private_key.d, 'private JWK should have d');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
// =====================================================================
|
|
112
|
+
// Symmetric Encrypt/Decrypt — Task 16
|
|
113
|
+
// =====================================================================
|
|
114
|
+
|
|
115
|
+
test('joseEncrypt + joseDecrypt round-trip with A256GCM', () => {
|
|
116
|
+
// A256GCM requires 32-byte key, 12-byte IV
|
|
117
|
+
const key = randomBytes(32).toString('hex');
|
|
118
|
+
const iv = randomBytes(12).toString('hex');
|
|
119
|
+
const plaintext = Buffer.from('Hello, JOSE!').toString('base64');
|
|
120
|
+
|
|
121
|
+
const encrypted = joseEncrypt(JoseContentEncryption.A256gcm, key, iv, plaintext);
|
|
122
|
+
assert.ok(encrypted.ciphertext, 'has ciphertext');
|
|
123
|
+
assert.ok(encrypted.tag, 'GCM has tag');
|
|
124
|
+
|
|
125
|
+
const decrypted = joseDecrypt(
|
|
126
|
+
JoseContentEncryption.A256gcm, key, iv,
|
|
127
|
+
encrypted.ciphertext, null, encrypted.tag,
|
|
128
|
+
);
|
|
129
|
+
assert.equal(decrypted, plaintext, 'decrypted matches original plaintext');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('joseEncrypt + joseDecrypt round-trip with A128GCM', () => {
|
|
133
|
+
const key = randomBytes(16).toString('hex');
|
|
134
|
+
const iv = randomBytes(12).toString('hex');
|
|
135
|
+
const plaintext = Buffer.from('test message 128').toString('base64');
|
|
136
|
+
|
|
137
|
+
const encrypted = joseEncrypt(JoseContentEncryption.A128gcm, key, iv, plaintext);
|
|
138
|
+
assert.ok(encrypted.ciphertext);
|
|
139
|
+
assert.ok(encrypted.tag);
|
|
140
|
+
|
|
141
|
+
const decrypted = joseDecrypt(
|
|
142
|
+
JoseContentEncryption.A128gcm, key, iv,
|
|
143
|
+
encrypted.ciphertext, null, encrypted.tag,
|
|
144
|
+
);
|
|
145
|
+
assert.equal(decrypted, plaintext);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('joseEncrypt + joseDecrypt with AAD', () => {
|
|
149
|
+
const key = randomBytes(32).toString('hex');
|
|
150
|
+
const iv = randomBytes(12).toString('hex');
|
|
151
|
+
const plaintext = Buffer.from('AAD test').toString('base64');
|
|
152
|
+
const aad = 'additional-authenticated-data';
|
|
153
|
+
|
|
154
|
+
const encrypted = joseEncrypt(JoseContentEncryption.A256gcm, key, iv, plaintext, aad);
|
|
155
|
+
assert.ok(encrypted.ciphertext);
|
|
156
|
+
|
|
157
|
+
const decrypted = joseDecrypt(
|
|
158
|
+
JoseContentEncryption.A256gcm, key, iv,
|
|
159
|
+
encrypted.ciphertext, aad, encrypted.tag,
|
|
160
|
+
);
|
|
161
|
+
assert.equal(decrypted, plaintext, 'decrypted with AAD matches');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('joseEncrypt + joseDecrypt with A256CBC-HS512', () => {
|
|
165
|
+
// A256CBC-HS512 requires 64-byte key, 16-byte IV
|
|
166
|
+
const key = randomBytes(64).toString('hex');
|
|
167
|
+
const iv = randomBytes(16).toString('hex');
|
|
168
|
+
const plaintext = Buffer.from('CBC mode test').toString('base64');
|
|
169
|
+
|
|
170
|
+
const encrypted = joseEncrypt(JoseContentEncryption.A256cbcHs512, key, iv, plaintext);
|
|
171
|
+
assert.ok(encrypted.ciphertext, 'has ciphertext');
|
|
172
|
+
|
|
173
|
+
const decrypted = joseDecrypt(
|
|
174
|
+
JoseContentEncryption.A256cbcHs512, key, iv,
|
|
175
|
+
encrypted.ciphertext, null, encrypted.tag,
|
|
176
|
+
);
|
|
177
|
+
assert.equal(decrypted, plaintext);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('joseDecrypt with wrong key fails', () => {
|
|
181
|
+
const key = randomBytes(32).toString('hex');
|
|
182
|
+
const wrongKey = randomBytes(32).toString('hex');
|
|
183
|
+
const iv = randomBytes(12).toString('hex');
|
|
184
|
+
const plaintext = Buffer.from('secret').toString('base64');
|
|
185
|
+
|
|
186
|
+
const encrypted = joseEncrypt(JoseContentEncryption.A256gcm, key, iv, plaintext);
|
|
187
|
+
|
|
188
|
+
assert.throws(
|
|
189
|
+
() => joseDecrypt(JoseContentEncryption.A256gcm, wrongKey, iv, encrypted.ciphertext, null, encrypted.tag),
|
|
190
|
+
/joseDecrypt failed/,
|
|
191
|
+
'wrong key should fail',
|
|
192
|
+
);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
// =====================================================================
|
|
197
|
+
// JWE — Encrypt/Decrypt JSON — Task 17
|
|
198
|
+
// =====================================================================
|
|
199
|
+
|
|
200
|
+
test('generalEncryptJson + decryptJson round-trip with ECDH-ES+A256KW', () => {
|
|
201
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
202
|
+
// JWK needs a kid for encryption
|
|
203
|
+
jwk.kid = 'test-key-1';
|
|
204
|
+
|
|
205
|
+
const payload = { hello: 'world', number: 42 };
|
|
206
|
+
|
|
207
|
+
const jwe = generalEncryptJson(
|
|
208
|
+
JoseKeyEncryption.EcdhEsA256kw,
|
|
209
|
+
JoseContentEncryption.A256gcm,
|
|
210
|
+
payload,
|
|
211
|
+
[jwk],
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
assert.ok(jwe, 'generalEncryptJson returned a result');
|
|
215
|
+
assert.ok(jwe.protected, 'JWE has protected header');
|
|
216
|
+
assert.ok(jwe.recipients, 'JWE has recipients');
|
|
217
|
+
assert.ok(jwe.iv, 'JWE has IV');
|
|
218
|
+
assert.ok(jwe.ciphertext, 'JWE has ciphertext');
|
|
219
|
+
assert.ok(jwe.tag, 'JWE has tag');
|
|
220
|
+
|
|
221
|
+
const decrypted = decryptJson(jwe, jwk);
|
|
222
|
+
assert.deepStrictEqual(decrypted, payload, 'decrypted payload matches original');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test('generalEncryptJson + decryptJson with X25519', () => {
|
|
226
|
+
const jwk = generateJwk(JoseNamedCurve.X25519);
|
|
227
|
+
jwk.kid = 'x25519-key';
|
|
228
|
+
|
|
229
|
+
const payload = { msg: 'encrypted with X25519' };
|
|
230
|
+
|
|
231
|
+
const jwe = generalEncryptJson(
|
|
232
|
+
JoseKeyEncryption.EcdhEsA256kw,
|
|
233
|
+
JoseContentEncryption.A256gcm,
|
|
234
|
+
payload,
|
|
235
|
+
[jwk],
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const decrypted = decryptJson(jwe, jwk);
|
|
239
|
+
assert.deepStrictEqual(decrypted, payload);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('generalEncryptJson with didcomm flag', () => {
|
|
243
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
244
|
+
jwk.kid = 'didcomm-key';
|
|
245
|
+
|
|
246
|
+
const payload = { type: 'didcomm-message' };
|
|
247
|
+
|
|
248
|
+
const jwe = generalEncryptJson(
|
|
249
|
+
JoseKeyEncryption.EcdhEsA256kw,
|
|
250
|
+
JoseContentEncryption.A256gcm,
|
|
251
|
+
payload,
|
|
252
|
+
[jwk],
|
|
253
|
+
true, // didcomm flag
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
assert.ok(jwe, 'didcomm JWE created');
|
|
257
|
+
const decrypted = decryptJson(jwe, jwk);
|
|
258
|
+
assert.deepStrictEqual(decrypted, payload);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test('generalEncryptJson with multiple recipients', () => {
|
|
262
|
+
const jwk1 = generateJwk(JoseNamedCurve.P256);
|
|
263
|
+
jwk1.kid = 'recipient-1';
|
|
264
|
+
const jwk2 = generateJwk(JoseNamedCurve.P256);
|
|
265
|
+
jwk2.kid = 'recipient-2';
|
|
266
|
+
|
|
267
|
+
const payload = { data: 'for multiple recipients' };
|
|
268
|
+
|
|
269
|
+
const jwe = generalEncryptJson(
|
|
270
|
+
JoseKeyEncryption.EcdhEsA256kw,
|
|
271
|
+
JoseContentEncryption.A256gcm,
|
|
272
|
+
payload,
|
|
273
|
+
[jwk1, jwk2],
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
assert.ok(jwe.recipients, 'has recipients');
|
|
277
|
+
assert.equal(jwe.recipients.length, 2, 'has 2 recipients');
|
|
278
|
+
|
|
279
|
+
// Both recipients can decrypt
|
|
280
|
+
const decrypted1 = decryptJson(jwe, jwk1);
|
|
281
|
+
assert.deepStrictEqual(decrypted1, payload, 'recipient 1 can decrypt');
|
|
282
|
+
|
|
283
|
+
const decrypted2 = decryptJson(jwe, jwk2);
|
|
284
|
+
assert.deepStrictEqual(decrypted2, payload, 'recipient 2 can decrypt');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
// =====================================================================
|
|
289
|
+
// JWS Compact — Sign/Verify — Task 18
|
|
290
|
+
// =====================================================================
|
|
291
|
+
|
|
292
|
+
test('compactSignJson + compactJsonVerify round-trip with ES256', () => {
|
|
293
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
294
|
+
const payload = { sub: '1234567890', name: 'Test User', iat: 1516239022 };
|
|
295
|
+
|
|
296
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Es256, payload, jwk);
|
|
297
|
+
|
|
298
|
+
assert.ok(typeof jws === 'string', 'compact JWS is a string');
|
|
299
|
+
assert.equal(jws.split('.').length, 3, 'JWS has 3 parts (header.payload.signature)');
|
|
300
|
+
|
|
301
|
+
const verified = compactJsonVerify(jws, jwk);
|
|
302
|
+
assert.deepStrictEqual(verified, payload, 'verified payload matches original');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('compactSignJson + compactJsonVerify with EdDSA', () => {
|
|
306
|
+
const jwk = generateJwk(JoseNamedCurve.Ed25519);
|
|
307
|
+
const payload = { claim: 'EdDSA signed' };
|
|
308
|
+
|
|
309
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Eddsa, payload, jwk);
|
|
310
|
+
const verified = compactJsonVerify(jws, jwk);
|
|
311
|
+
assert.deepStrictEqual(verified, payload);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('compactSignJson + compactJsonVerify with ES384', () => {
|
|
315
|
+
const jwk = generateJwk(JoseNamedCurve.P384);
|
|
316
|
+
const payload = { alg: 'ES384 test' };
|
|
317
|
+
|
|
318
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Es384, payload, jwk);
|
|
319
|
+
const verified = compactJsonVerify(jws, jwk);
|
|
320
|
+
assert.deepStrictEqual(verified, payload);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('compactSignJson with didcomm flag', () => {
|
|
324
|
+
const jwk = generateJwk(JoseNamedCurve.Ed25519);
|
|
325
|
+
const payload = { type: 'didcomm-signed' };
|
|
326
|
+
|
|
327
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Eddsa, payload, jwk, true);
|
|
328
|
+
assert.ok(typeof jws === 'string');
|
|
329
|
+
|
|
330
|
+
const verified = compactJsonVerify(jws, jwk);
|
|
331
|
+
assert.deepStrictEqual(verified, payload);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('compactJsonVerify with wrong key fails', () => {
|
|
335
|
+
const jwk1 = generateJwk(JoseNamedCurve.P256);
|
|
336
|
+
const jwk2 = generateJwk(JoseNamedCurve.P256);
|
|
337
|
+
const payload = { msg: 'should fail' };
|
|
338
|
+
|
|
339
|
+
const jws = compactSignJson(JoseSigningAlgorithm.Es256, payload, jwk1);
|
|
340
|
+
|
|
341
|
+
assert.throws(
|
|
342
|
+
() => compactJsonVerify(jws, jwk2),
|
|
343
|
+
/compactJsonVerify failed/,
|
|
344
|
+
'wrong key should fail verification',
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
// =====================================================================
|
|
350
|
+
// JWS Flattened — Sign/Verify
|
|
351
|
+
// =====================================================================
|
|
352
|
+
|
|
353
|
+
test('flattenedSignJson + jsonVerify round-trip with ES256', () => {
|
|
354
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
355
|
+
jwk.kid = 'flattened-key-1';
|
|
356
|
+
const payload = { data: 'flattened sign test' };
|
|
357
|
+
|
|
358
|
+
const jws = flattenedSignJson(JoseSigningAlgorithm.Es256, payload, jwk);
|
|
359
|
+
|
|
360
|
+
assert.ok(jws, 'flattenedSignJson returned a result');
|
|
361
|
+
assert.ok(jws.protected, 'has protected header');
|
|
362
|
+
assert.ok(jws.payload, 'has payload');
|
|
363
|
+
assert.ok(jws.signature, 'has signature');
|
|
364
|
+
|
|
365
|
+
const verified = jsonVerify(jws, jwk);
|
|
366
|
+
assert.deepStrictEqual(verified, payload, 'verified payload matches original');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test('flattenedSignJson + jsonVerify with EdDSA', () => {
|
|
370
|
+
const jwk = generateJwk(JoseNamedCurve.Ed25519);
|
|
371
|
+
jwk.kid = 'ed-flattened';
|
|
372
|
+
const payload = { curve: 'Ed25519', format: 'flattened' };
|
|
373
|
+
|
|
374
|
+
const jws = flattenedSignJson(JoseSigningAlgorithm.Eddsa, payload, jwk);
|
|
375
|
+
const verified = jsonVerify(jws, jwk);
|
|
376
|
+
assert.deepStrictEqual(verified, payload);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
// =====================================================================
|
|
381
|
+
// JWS General — Sign/Verify (multiple signers)
|
|
382
|
+
// =====================================================================
|
|
383
|
+
|
|
384
|
+
test('generalSignJson + jsonVerify round-trip with single signer', () => {
|
|
385
|
+
const jwk = generateJwk(JoseNamedCurve.P256);
|
|
386
|
+
jwk.kid = 'general-signer-1';
|
|
387
|
+
jwk.alg = 'ES256';
|
|
388
|
+
const payload = { data: 'general sign test' };
|
|
389
|
+
|
|
390
|
+
const jws = generalSignJson(payload, [jwk]);
|
|
391
|
+
|
|
392
|
+
assert.ok(jws, 'generalSignJson returned a result');
|
|
393
|
+
assert.ok(jws.signatures, 'has signatures array');
|
|
394
|
+
assert.equal(jws.signatures.length, 1, 'has 1 signature');
|
|
395
|
+
assert.ok(jws.payload, 'has payload');
|
|
396
|
+
|
|
397
|
+
const verified = jsonVerify(jws, jwk);
|
|
398
|
+
assert.deepStrictEqual(verified, payload, 'verified payload matches original');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('generalSignJson + jsonVerify with multiple signers', () => {
|
|
402
|
+
const jwk1 = generateJwk(JoseNamedCurve.P256);
|
|
403
|
+
jwk1.kid = 'multi-signer-1';
|
|
404
|
+
jwk1.alg = 'ES256';
|
|
405
|
+
const jwk2 = generateJwk(JoseNamedCurve.P384);
|
|
406
|
+
jwk2.kid = 'multi-signer-2';
|
|
407
|
+
jwk2.alg = 'ES384';
|
|
408
|
+
|
|
409
|
+
const payload = { data: 'multi-signer test' };
|
|
410
|
+
|
|
411
|
+
const jws = generalSignJson(payload, [jwk1, jwk2]);
|
|
412
|
+
assert.equal(jws.signatures.length, 2, 'has 2 signatures');
|
|
413
|
+
|
|
414
|
+
// Each signer can independently verify
|
|
415
|
+
const verified1 = jsonVerify(jws, jwk1);
|
|
416
|
+
assert.deepStrictEqual(verified1, payload, 'signer 1 verifies');
|
|
417
|
+
|
|
418
|
+
const verified2 = jsonVerify(jws, jwk2);
|
|
419
|
+
assert.deepStrictEqual(verified2, payload, 'signer 2 verifies');
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test('generalSignJson with didcomm flag', () => {
|
|
423
|
+
const jwk = generateJwk(JoseNamedCurve.Ed25519);
|
|
424
|
+
jwk.kid = 'didcomm-signer';
|
|
425
|
+
jwk.alg = 'EdDSA';
|
|
426
|
+
const payload = { type: 'didcomm-general' };
|
|
427
|
+
|
|
428
|
+
const jws = generalSignJson(payload, [jwk], true);
|
|
429
|
+
assert.ok(jws.signatures);
|
|
430
|
+
|
|
431
|
+
const verified = jsonVerify(jws, jwk);
|
|
432
|
+
assert.deepStrictEqual(verified, payload);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
// =====================================================================
|
|
437
|
+
// Enum values match ffi-jose
|
|
438
|
+
// =====================================================================
|
|
439
|
+
|
|
440
|
+
test('JoseNamedCurve enum values', () => {
|
|
441
|
+
assert.equal(JoseNamedCurve.P256, 0);
|
|
442
|
+
assert.equal(JoseNamedCurve.P384, 1);
|
|
443
|
+
assert.equal(JoseNamedCurve.P521, 2);
|
|
444
|
+
assert.equal(JoseNamedCurve.Secp256k1, 3);
|
|
445
|
+
assert.equal(JoseNamedCurve.Ed25519, 4);
|
|
446
|
+
assert.equal(JoseNamedCurve.Ed448, 5);
|
|
447
|
+
assert.equal(JoseNamedCurve.X25519, 6);
|
|
448
|
+
assert.equal(JoseNamedCurve.X448, 7);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test('JoseContentEncryption enum values', () => {
|
|
452
|
+
assert.equal(JoseContentEncryption.A128gcm, 0);
|
|
453
|
+
assert.equal(JoseContentEncryption.A192gcm, 1);
|
|
454
|
+
assert.equal(JoseContentEncryption.A256gcm, 2);
|
|
455
|
+
assert.equal(JoseContentEncryption.A128cbcHs256, 3);
|
|
456
|
+
assert.equal(JoseContentEncryption.A192cbcHs384, 4);
|
|
457
|
+
assert.equal(JoseContentEncryption.A256cbcHs512, 5);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('JoseKeyEncryption enum values', () => {
|
|
461
|
+
assert.equal(JoseKeyEncryption.Dir, 0);
|
|
462
|
+
assert.equal(JoseKeyEncryption.EcdhEs, 1);
|
|
463
|
+
assert.equal(JoseKeyEncryption.EcdhEsA128kw, 2);
|
|
464
|
+
assert.equal(JoseKeyEncryption.EcdhEsA192kw, 3);
|
|
465
|
+
assert.equal(JoseKeyEncryption.EcdhEsA256kw, 4);
|
|
466
|
+
assert.equal(JoseKeyEncryption.Rsa1_5, 5);
|
|
467
|
+
assert.equal(JoseKeyEncryption.RsaOaep, 6);
|
|
468
|
+
assert.equal(JoseKeyEncryption.RsaOaep256, 7);
|
|
469
|
+
assert.equal(JoseKeyEncryption.RsaOaep384, 8);
|
|
470
|
+
assert.equal(JoseKeyEncryption.RsaOaep512, 9);
|
|
471
|
+
assert.equal(JoseKeyEncryption.Pbes2Hs256A128kw, 10);
|
|
472
|
+
assert.equal(JoseKeyEncryption.Pbes2Hs384A192kw, 11);
|
|
473
|
+
assert.equal(JoseKeyEncryption.Pbes2Hs512A256kw, 12);
|
|
474
|
+
assert.equal(JoseKeyEncryption.A128kw, 13);
|
|
475
|
+
assert.equal(JoseKeyEncryption.A192kw, 14);
|
|
476
|
+
assert.equal(JoseKeyEncryption.A256kw, 15);
|
|
477
|
+
assert.equal(JoseKeyEncryption.A128gcmkw, 16);
|
|
478
|
+
assert.equal(JoseKeyEncryption.A192gcmkw, 17);
|
|
479
|
+
assert.equal(JoseKeyEncryption.A256gcmkw, 18);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('JoseSigningAlgorithm enum values', () => {
|
|
483
|
+
assert.equal(JoseSigningAlgorithm.Es256, 0);
|
|
484
|
+
assert.equal(JoseSigningAlgorithm.Es384, 1);
|
|
485
|
+
assert.equal(JoseSigningAlgorithm.Es512, 2);
|
|
486
|
+
assert.equal(JoseSigningAlgorithm.Es256k, 3);
|
|
487
|
+
assert.equal(JoseSigningAlgorithm.Eddsa, 4);
|
|
488
|
+
assert.equal(JoseSigningAlgorithm.Hs256, 5);
|
|
489
|
+
assert.equal(JoseSigningAlgorithm.Hs384, 6);
|
|
490
|
+
assert.equal(JoseSigningAlgorithm.Hs512, 7);
|
|
491
|
+
assert.equal(JoseSigningAlgorithm.Rs256, 8);
|
|
492
|
+
assert.equal(JoseSigningAlgorithm.Rs384, 9);
|
|
493
|
+
assert.equal(JoseSigningAlgorithm.Rs512, 10);
|
|
494
|
+
assert.equal(JoseSigningAlgorithm.Ps256, 11);
|
|
495
|
+
assert.equal(JoseSigningAlgorithm.Ps384, 12);
|
|
496
|
+
assert.equal(JoseSigningAlgorithm.Ps512, 13);
|
|
497
|
+
});
|
|
@@ -21,7 +21,7 @@ const jsonld = require('jsonld');
|
|
|
21
21
|
const { extendContextLoader } = require('jsonld-signatures');
|
|
22
22
|
|
|
23
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
-
const dataDir = resolve(__dirname, '
|
|
24
|
+
const dataDir = resolve(__dirname, 'test-data');
|
|
25
25
|
const loadJson = (name) => JSON.parse(readFileSync(resolve(dataDir, name), 'utf8'));
|
|
26
26
|
|
|
27
27
|
const inputDocument = loadJson('inputDocument.json');
|