git ssb

1+

Dominic / ssb-private-groups



Tree: 86f3b92eaac8f991e0ec002cb08d0b0120608e21

Files: 86f3b92eaac8f991e0ec002cb08d0b0120608e21 / index.js

4600 bytesRaw
1function isBox2(ctxt) {
2 //we can just do a fairly lax check here, don't check the content
3 //is canonical base64, because that check has already been done.
4 return 'string' == typeof ctxt && /\.box2$/.test(ctxt)
5}
6
7function ctxtToBuffer(ctxt) {
8 return isBox2(ctxt) && Buffer.from(ctxt.substring(ctxt.indexOf('.')), 'base64')
9}
10
11function idToBuffer (id) {
12 return Buffer.from(id.substring(1, id.indexOf('.')), 'base64')
13}
14
15//by deriving the message key from the group id (the founding
16//message id) and the unbox key for that message, this ensures
17//someone can't decrypt the message without knowing the founding
18//message, therefore avoiding surruptious sharing the group.
19//they can't _not_ know who made the group, so if someone else
20//shares it to them, they know they are being sneaky.
21
22//and, you can verify this property from the design! you can't
23//rewrite this code so they don't know the founding message
24//and still be able to decrypt these messages.
25
26function getGroupMsgKey(previous, group) {
27 return hmac(Buffer.concat([previous, group.id]), group.unbox)
28}
29
30exports.init = function () {
31
32 var af = AtomicFile(
33 path.join(config.path, 'private-groups/local-keys.json')
34 )
35 var ready = false, waiting = []
36 af.get(function (err, data) {
37 keyState = data || {msgKeys: [], groupKeys: []}
38 ready = true
39 while(waiting.length) waiting.shift()()
40 })
41
42 //state:
43 /*
44 {
45 <author>: {
46 key: <author's latest privacy key>
47 }
48 }
49 */
50
51 var keyState = null
52 var state = null
53 //cache: {<author>: [scalar_mult(msg_keys[i], <author's latest privacy key>)]
54 var cache = {}
55
56
57 sbot._flumeUse('private-groups/remote-keys', Reduce(1, function (acc, msg) {
58 state = acc = acc || {}
59 if(msg.content.type === 'private-msg-key') {
60 acc[msg.author] = [{sequence: msg.sequence, key: msg.content.key}]
61 cache[msg.author] = null
62 }
63 })
64
65 //sbot._flumeUse('private-groups/old-remote-keys', Level(1, function (data) {
66 // if(msg.content.type === 'private-msg-key') {
67 // return [msg.author, msg.sequence, msg.content.type]
68 // }
69 //})
70
71 sbot.addMap(function (data, cb) {
72 if(!isBox2(data.value.content)) return cb(null, data)
73 //the views and keyState have not been loaded
74 //delay processing any box2 messages until they are.
75 if(ready) cb(null, data)
76 else waiting.push(function () {
77 cb(null, data)
78 })
79 })
80
81 sbot.addUnboxer({
82 key:
83 function (content, value) {
84 if(!isBox2(content)) return
85 var a_state = state[value.author]
86 if(!a_state) return
87
88 var keys_to_try = cache[value.author]
89 if(!keys_to_try) {
90 keys_to_try = cache[value.author] = keyState.msgKeys.map(function (key) {
91 return scalarmult(a_state.key, curve.private)
92 })
93
94 var ctxt = ctxtToBuffer(content), nonce = idToBuffer(value.previous)
95 var key = groupbox.unboxKey( //direct recipients
96 ctxt, nonce, keys_to_try, 8
97 )
98 if(key) return key
99
100 var group_keys = []
101 for(var id in keyState.groupKeys)
102 group_keys.push(getGroupMsgKey(nonce, keyState.groupKeys[id])
103 //note: if we only allow groups in the first 4 slots
104 //that means better sort them before any individuals
105 key = groupbox.unboxKey( //groups we are in
106 ctxt, nonce, group_keys, 4
107 )
108 if(key) return key
109 },
110 value: function (content, key) {
111 if(!isBox2(content)) return
112 return groupbox.unboxBody(content, key)
113 }
114 })
115
116 return {
117// addGroupKey: function (group, cb) {
118// af.get(function () {
119// keyState.groupKeys[hmac(group.id, group.unbox)] = group)
120// af.set(keys, cb)
121// })
122// },
123 addCurvePair: function (curve_keys, cb) {
124 if(!isCurvePair(curve_keys)) return cb(new Error('expected a pair of curve25519 keys')
125 keyState.msgKeys.push(curve_keys)
126 cache = {} //clear cache, so it's regenerated up to date.
127 //NOTE: identiy adding this key must publish
128 // a message advertising this receive key, or no one
129 // will send them messages!
130 af.set(msg_keys, cb)
131 },
132//forgetting old keys. crude basis for forward secrecy.
133//you will no longer be able to decrypt messages sent to curve_pk
134 forget: function (curve_pk, cb) {
135 af.get(function () {
136 for(var i = msg_keys.length-1; i >= 0; i--)
137 if(curve_pk == msg_keys[i].public)
138 msg_keys.splice(i, 1)
139 cache = {} //clear cache, will be regenerated.
140 af.set(msg_keys, cb)
141 })
142 }
143 }
144}
145
146
147
148
149
150
151
152

Built with git-ssb-web