Files: d62aabe95512e6a3e72bd54fe3021aa77a550401 / index.js
5890 bytesRaw
1 | var AtomicFile = require('atomic-file') |
2 | var path = require('path') |
3 | var Reduce = require('flumeview-reduce') |
4 | var group_box = require('group-box') |
5 | var mkdirp = require('mkdirp') |
6 | var u = require('./util') |
7 | var cl = require('chloride') |
8 | |
9 | //by deriving the message key from the group id (the founding |
10 | //message id) and the unbox key for that message, this ensures |
11 | //someone can't decrypt the message without knowing the founding |
12 | //message, therefore avoiding surruptious sharing the group. |
13 | //they can't _not_ know who made the group, so if someone else |
14 | //shares it to them, they know they are being sneaky. |
15 | |
16 | //and, you can verify this property from the design! you can't |
17 | //rewrite this code so they don't know the founding message |
18 | //and still be able to decrypt these messages. |
19 | |
20 | function getGroupMsgKey(previous, group) { |
21 | return hmac(Buffer.concat([previous, group.id]), group.unbox) |
22 | } |
23 | |
24 | exports.name = 'private-groups' |
25 | |
26 | exports.init = function (sbot, config) { |
27 | |
28 | var dir = path.join(config.path, 'private-groups') |
29 | |
30 | var af = AtomicFile(path.join(dir, 'local-keys.json')) |
31 | var ready = false, waiting = [] |
32 | mkdirp(dir, function () { |
33 | af.get(function (err, data) { |
34 | keyState = data || {msgKeys: [], groupKeys: []} |
35 | ready = true |
36 | while(waiting.length) waiting.shift()() |
37 | }) |
38 | }) |
39 | |
40 | function onReady (fn) { |
41 | if(ready) fn() |
42 | else waiting.push(fn) |
43 | } |
44 | |
45 | //state: |
46 | /* |
47 | { |
48 | <author>: { |
49 | key: <author's latest privacy key> |
50 | } |
51 | } |
52 | */ |
53 | |
54 | var keyState = null |
55 | var state = null |
56 | //cache: {<author>: [scalar_mult(msg_keys[i], <author's latest privacy key>)] |
57 | var cache = {} |
58 | |
59 | |
60 | sbot._flumeUse('private-groups/remote-keys', Reduce(1, function (acc, data) { |
61 | state = acc = acc || {} |
62 | var msg = data.value |
63 | if(msg.content.type === 'private-msg-key') { |
64 | acc[msg.author] = [{sequence: msg.sequence, key: msg.content.key}] |
65 | cache[msg.author] = null |
66 | } |
67 | })) |
68 | |
69 | //sbot._flumeUse('private-groups/old-remote-keys', Level(1, function (data) { |
70 | // if(msg.content.type === 'private-msg-key') { |
71 | // return [msg.author, msg.sequence, msg.content.type] |
72 | // } |
73 | //}) |
74 | |
75 | sbot.addMap(function (data, cb) { |
76 | if(!u.isBox2(data.value.content)) return cb(null, data) |
77 | //the views and keyState have not been loaded |
78 | //delay processing any box2 messages until they are. |
79 | if(ready) cb(null, data) |
80 | else waiting.push(function () { |
81 | cb(null, data) |
82 | }) |
83 | }) |
84 | |
85 | sbot.addUnboxer({ |
86 | key: function (content, value) { |
87 | if(!u.isBox2(content)) return |
88 | //a_state is reverse chrono list of author's private-msg-keys |
89 | //take the latest key that has sequence less than message |
90 | //to decrypt |
91 | var a_state = state[value.author] |
92 | if(!a_state) return |
93 | |
94 | var keys_to_try = cache[value.author] |
95 | var a_key |
96 | for(var i = 0; i < a_state.length; i++) { |
97 | if(a_state[i].sequence < value.sequence) { |
98 | a_key = a_state[i].key |
99 | break; |
100 | } |
101 | } |
102 | if(!a_key) return |
103 | |
104 | if(!keys_to_try) |
105 | keys_to_try = cache[value.author] = keyState.msgKeys.map(function (curve) { |
106 | console.log("A_KEY", a_key, curve) |
107 | return cl.crypto_scalarmult( |
108 | Buffer.from(curve.private, 'base64'), |
109 | Buffer.from(a_key, 'base64') |
110 | ) |
111 | }) |
112 | |
113 | var ctxt = u.ctxt2Buffer(content), nonce = u.id2Buffer(value.previous) |
114 | // console.log('-CTXT', ctxt) |
115 | // console.log('-NONCE', nonce) |
116 | // console.log('-KEYS', keys_to_try) |
117 | var key = group_box.unboxKey( //direct recipients |
118 | ctxt, nonce, keys_to_try, 8 |
119 | ) |
120 | if(key) return key |
121 | |
122 | var group_keys = [] |
123 | for(var id in keyState.groupKeys) |
124 | group_keys.push(getGroupMsgKey(nonce, keyState.groupKeys[id])) |
125 | //note: if we only allow groups in the first 4 slots |
126 | //that means better sort them before any individuals |
127 | key = group_box.unboxKey( //groups we are in |
128 | ctxt, nonce, group_keys, 4 |
129 | ) |
130 | if(key) return key |
131 | }, |
132 | value: function (content, key, value) { |
133 | if(!u.isBox2(content)) return |
134 | console.log() |
135 | console.log('-------------:', value) |
136 | var ctxt = u.ctxt2Buffer(content) |
137 | var nonce = u.id2Buffer(value.previous) |
138 | console.log("INPUT", { |
139 | ctxt: ctxt.toString('hex'), |
140 | nonce: nonce, |
141 | key: key |
142 | }) |
143 | console.log( |
144 | 'INPUT_VALUE', |
145 | cl.crypto_hash_sha256(Buffer.concat([ctxt, nonce, key])), |
146 | group_box.unboxBody(ctxt, nonce, key) |
147 | ) |
148 | var ptxt = group_box.unboxBody(ctxt, nonce, key) |
149 | if(ptxt) { |
150 | try { |
151 | console.log("CONTENT", JSON.parse(ptxt.toString())) |
152 | return JSON.parse(ptxt.toString()) |
153 | } catch (_) {} |
154 | } |
155 | } |
156 | }) |
157 | |
158 | return { |
159 | // addGroupKey: function (group, cb) { |
160 | // af.get(function () { |
161 | // keyState.groupKeys[hmac(group.id, group.unbox)] = group) |
162 | // af.set(keys, cb) |
163 | // }) |
164 | // }, |
165 | addCurvePair: function (curve_keys, cb) { |
166 | onReady(function () { |
167 | if(!u.isCurvePair(curve_keys)) |
168 | return cb(new Error('expected a pair of curve25519 keys')) |
169 | keyState.msgKeys.push(curve_keys) |
170 | cache = {} //clear cache, so it's regenerated up to date. |
171 | //NOTE: identiy adding this key must publish |
172 | // a message advertising this receive key, or no one |
173 | // will send them messages! |
174 | af.set(keyState, cb) |
175 | }) |
176 | }, |
177 | //forgetting old keys. crude basis for forward secrecy. |
178 | //you will no longer be able to decrypt messages sent to curve_pk |
179 | forget: function (curve_pk, cb) { |
180 | af.get(function () { |
181 | for(var i = msg_keys.length-1; i >= 0; i--) |
182 | if(curve_pk == msg_keys[i].public) |
183 | msg_keys.splice(i, 1) |
184 | cache = {} //clear cache, will be regenerated. |
185 | af.set(msg_keys, cb) |
186 | }) |
187 | } |
188 | } |
189 | } |
190 | |
191 | |
192 | |
193 | |
194 | |
195 | |
196 |
Built with git-ssb-web