Skip to content

Commit 3852080

Browse files
committed
Add JSON marshal/unmarshal
1 parent baa209b commit 3852080

File tree

11 files changed

+98
-34
lines changed

11 files changed

+98
-34
lines changed

index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
members = [];
3838
renderers = [];
3939

40-
let leaf = crypto.getRandomValues(new Uint8Array(32));
40+
let leaf = base64.random(32);
4141
let creator = await StateType.oneMemberGroup(leaf);
4242
addMember(creator);
4343
}
@@ -87,7 +87,7 @@
8787

8888
async function userAdd() {
8989
let last = members[members.length - 1];
90-
let leaf = crypto.getRandomValues(new Uint8Array(32));
90+
let leaf = base64.random(32);
9191
let gik = last.groupInitKey
9292
let ua = await StateType.join(leaf, gik);
9393

@@ -103,7 +103,7 @@
103103
async function groupAdd() {
104104
let last = members[members.length - 1];
105105

106-
let initLeaf = crypto.getRandomValues(new Uint8Array(4));
106+
let initLeaf = base64.random(32);
107107
let initKP = await iota(initLeaf);
108108
let ga = await last.add(initKP.publicKey);
109109

@@ -117,7 +117,7 @@
117117
}
118118

119119
async function update(k) {
120-
let leaf = crypto.getRandomValues(new Uint8Array(32));
120+
let leaf = base64.random(32);
121121
let update = await members[k].update(leaf);
122122

123123
for (m2 of members) {

src/art-state.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ class ARTState {
2020
return this.art.nodes;
2121
}
2222

23+
static fromJSON(obj) {
24+
let out = new ARTState();
25+
out.art = ART.fromJSON(obj.art);
26+
return out;
27+
}
28+
2329
static async oneMemberGroup(leaf) {
2430
let state = new ARTState();
2531
state.art = await ART.oneMemberGroup(leaf);

src/art.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@ class ART {
1313
*/
1414
constructor() {
1515
this.size = 0;
16+
this.index = 0;
1617
this.nodes = [];
1718
}
1819

20+
static fromJSON(obj) {
21+
let out = new ART();
22+
out.size = obj.size;
23+
out.index = obj.index;
24+
out.nodes = obj.nodes;
25+
return out;
26+
}
27+
1928
/*
2029
* Construct an ART representing a group with a single member,
2130
* with the given leaf secret.

src/base64.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
module.exports = {
4-
stringify: function (a) {
4+
stringify: function(a) {
55
if (a instanceof ArrayBuffer) {
66
a = new Uint8Array(a);
77
}
@@ -12,11 +12,16 @@ module.exports = {
1212
.replace(/\//g, "_");
1313
},
1414

15-
parse: function (s) {
15+
parse: function(s) {
1616
s = s.replace(/-/g, "+")
1717
.replace(/_/g, "/")
1818
.replace(/\s/g, '');
1919
let u = new Uint8Array(Array.prototype.map.call(atob(s), c => c.charCodeAt(0)));
2020
return u.buffer;
2121
},
22+
23+
random: function(n) {
24+
const b = crypto.getRandomValues(new Uint8Array(n));
25+
return this.stringify(b);
26+
},
2227
};

src/dh.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ async function _export(pub) {
2727
* }
2828
*/
2929
async function newKeyPair() {
30-
const kp = await cs.generateKey(ECDH_GEN, false, ECDH_KEY_USAGES);
30+
const kp = await cs.generateKey(ECDH_GEN, true, ECDH_KEY_USAGES);
3131
return {
32-
privateKey: kp.privateKey,
32+
privateKey: await _export(kp.privateKey),
3333
publicKey: await _export(kp.publicKey),
3434
};
3535
}
@@ -41,14 +41,17 @@ async function newKeyPair() {
4141
*
4242
* Returns: Promise resolving to ArrayBuffer
4343
*/
44-
async function secret(priv, pubJWK) {
44+
async function secret(privJWK, pubJWK) {
45+
const priv = await _import(privJWK);
4546
const pub = await _import(pubJWK);
4647
const alg = {
4748
name: "ECDH",
4849
namedCurve: "P-256",
4950
public: pub,
5051
};
51-
return await cs.deriveBits(alg, priv, SECRET_BITS);
52+
53+
const ss = await cs.deriveBits(alg, priv, SECRET_BITS);
54+
return base64.stringify(ss);
5255
}
5356

5457
/*
@@ -77,8 +80,8 @@ async function test() {
7780
const kpB = await newKeyPair();
7881

7982
console.log("secret...");
80-
const ssAB = base64.stringify(await secret(kpA.privateKey, kpB.publicKey));
81-
const ssBA = base64.stringify(await secret(kpB.privateKey, kpA.publicKey));
83+
const ssAB = await secret(kpA.privateKey, kpB.publicKey);
84+
const ssBA = await secret(kpB.privateKey, kpA.publicKey);
8285

8386
console.log("[DH]", (ssAB == ssBA)? "PASS" : "FAIL");
8487
} catch (err) {

src/eckem.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,16 @@ function AES_GCM_ENC(iv) {
2323
* ct: BufferSource
2424
* }
2525
*/
26-
async function encrypt(plaintext, pubA) {
26+
async function encrypt(pt64, pubA) {
27+
const pt = base64.parse(pt64);
2728
const kpE = await DH.newKeyPair();
28-
const ekData = await DH.secret(kpE.privateKey, pubA);
29+
const ekData64 = await DH.secret(kpE.privateKey, pubA);
30+
const ekData = base64.parse(ekData64);
2931
const ek = await cs.importKey("raw", ekData, "AES-GCM", false, ['encrypt']);
3032

3133
const iv = window.crypto.getRandomValues(new Uint8Array(12))
3234
const alg = { name: "AES-GCM", iv: iv };
33-
const ct = await cs.encrypt(alg, ek, plaintext);
35+
const ct = await cs.encrypt(alg, ek, pt);
3436

3537
return {
3638
pub: kpE.publicKey,
@@ -51,9 +53,11 @@ async function decrypt(ciphertext, priv) {
5153
const ct = base64.parse(ciphertext.ct);
5254
const alg = { name: "AES-GCM", iv: iv };
5355

54-
const ekData = await DH.secret(priv, ciphertext.pub);
56+
const ekData64 = await DH.secret(priv, ciphertext.pub);
57+
const ekData = base64.parse(ekData64);
5558
const ek = await cs.importKey("raw", ekData, "AES-GCM", false, ['decrypt']);
56-
return cs.decrypt(alg, ek, ct);
59+
const pt = await cs.decrypt(alg, ek, ct);
60+
return base64.stringify(pt);
5761
}
5862

5963
/*

src/iota.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const cs = window.crypto.subtle;
44
const EC = require('elliptic').ec;
55
const BN = require('bn.js');
6+
const base64 = require('./base64');
67

78
const p256 = new EC('p256');
89

@@ -17,8 +18,9 @@ function bn2b64(n) {
1718
.replace(/\//g, '_');
1819
}
1920

20-
async function iota(secret) {
21+
async function iota(secret64) {
2122
// Digest the input
23+
const secret = base64.parse(secret64);
2224
const digest = await cs.digest("SHA-256", secret);
2325

2426
// Convert it to an integer and compute the resulting key pair
@@ -40,7 +42,7 @@ async function iota(secret) {
4042
namedCurve: "P-256",
4143
};
4244
return {
43-
privateKey: await cs.importKey("jwk", privJWK, alg, false, ["deriveKey", "deriveBits"]),
45+
privateKey: privJWK,
4446
publicKey: pubJWK,
4547
}
4648
}

src/main.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
window.base64 = require('./base64');
12
window.DH = require('./dh');
23
window.ECKEM = require('./eckem');
34
window.iota = require('./iota');

src/state-tester.js

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const iota = require('./iota');
44
const DH = require('./dh');
5+
const base64 = require('./base64');
56

67
async function nodeEqual(lhs, rhs) {
78
let lfp = await DH.fingerprint(lhs.public);
@@ -52,11 +53,11 @@ async function groupDump(label, group) {
5253
async function testUserAdd(State) {
5354
const testGroupSize = 5;
5455

55-
let creator = await State.oneMemberGroup(new Uint8Array([0]));
56+
let creator = await State.oneMemberGroup(base64.random(32));
5657
let members = [creator];
5758

5859
for (let i = 1; i < testGroupSize; ++i) {
59-
let leaf = window.crypto.getRandomValues(new Uint8Array(32));
60+
let leaf = base64.random(32);
6061
let gik = members[members.length - 1].groupInitKey;
6162
let uaIn = await State.join(leaf, gik);
6263

@@ -85,11 +86,11 @@ async function testUserAdd(State) {
8586
async function testGroupAdd(State) {
8687
const testGroupSize = 5;
8788

88-
let creator = await State.oneMemberGroup(new Uint8Array([0]));
89+
let creator = await State.oneMemberGroup(base64.random(32));
8990
let members = [creator];
9091

9192
for (let i = 1; i < testGroupSize; ++i) {
92-
let initLeaf = window.crypto.getRandomValues(new Uint8Array(4));
93+
let initLeaf = base64.random(32);
9394
let initKP = await iota(initLeaf)
9495
let gaIn = await members[members.length - 1].add(initKP.publicKey)
9596

@@ -115,13 +116,15 @@ async function testGroupAdd(State) {
115116
console.log("[state-group-add] PASS");
116117
}
117118

118-
async function testUpdate(State) {
119+
async function testUpdate(State, transcode) {
120+
let label = (transcode)? 'state-json' : 'state-update';
121+
119122
// Create a group via GroupAdds
120123
const testGroupSize = 5;
121-
let creator = await State.oneMemberGroup(new Uint8Array([0]));
124+
let creator = await State.oneMemberGroup(base64.random(32));
122125
let members = [creator];
123126
for (let i = 1; i < testGroupSize; ++i) {
124-
let initLeaf = window.crypto.getRandomValues(new Uint8Array(4));
127+
let initLeaf = base64.random(32);
125128
let initKP = await iota(initLeaf);
126129
let ga = await members[members.length - 1].add(initKP.publicKey)
127130

@@ -133,9 +136,22 @@ async function testUpdate(State) {
133136
members.push(joiner);
134137
}
135138

139+
if (transcode) {
140+
let encoded = members.map(m => JSON.stringify(m));
141+
let decoded = encoded.map(e => JSON.parse(e));
142+
let revived = decoded.map(d => State.fromJSON(d));
143+
144+
let eqp = members.map(async (m, i) => groupEqual(m, revived[i]));
145+
let eqr = await Promise.all(eqp);
146+
let eq = eqr.reduce((x, y) => x && y);
147+
if (!eq) {
148+
throw label;
149+
}
150+
}
151+
136152
// Have each member update and verify that others are consistent
137153
for (let m1 of members) {
138-
let leaf = crypto.getRandomValues(new Uint8Array(32));
154+
let leaf = base64.random(32);
139155
let updateIn = await m1.update(leaf);
140156

141157
let updateEnc = JSON.stringify(updateIn);
@@ -154,18 +170,19 @@ async function testUpdate(State) {
154170
if (!eq) {
155171
await groupDump(m1.index, m1);
156172
await groupDump(m2.index, m2);
157-
throw 'state-group-add';
173+
throw label;
158174
}
159175
}
160176
}
161177

162-
console.log("[state-update] PASS");
178+
console.log(`[${label}] PASS`);
163179
}
164180

165181
module.exports = {
166182
test: async function(State) {
167183
await testUserAdd(State);
168184
await testGroupAdd(State);
169-
await testUpdate(State);
185+
await testUpdate(State, false);
186+
await testUpdate(State, true);
170187
},
171188
};

src/treekem-state.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ class TreeKEMState {
2626
async dump() {
2727
return this.tkem.dump();
2828
}
29+
30+
static fromJSON(obj) {
31+
let out = new TreeKEMState();
32+
out.tkem = TreeKEM.fromJSON(obj.tkem);
33+
return out;
34+
}
2935

3036
static async oneMemberGroup(leaf) {
3137
let state = new TreeKEMState();
@@ -57,7 +63,7 @@ class TreeKEMState {
5763
}
5864

5965
async add(userInitPub) {
60-
let leaf = window.crypto.getRandomValues(new Uint8Array(32));
66+
let leaf = base64.random(32);
6167
let encryptedLeaf = await ECKEM.encrypt(leaf, userInitPub);
6268

6369
let gik = this.groupInitKey;

0 commit comments

Comments
 (0)