git ssb

1+

Dominic / ssb-private-groups



Commit 86f3b92eaac8f991e0ec002cb08d0b0120608e21

initial

Dominic Tarr committed on 12/27/2018, 9:19:59 AM

Files changed

LICENSEadded
README.mdadded
index.jsadded
package.jsonadded
LICENSEView
@@ -1,0 +1,22 @@
1 +Copyright (c) 2018 Dominic Tarr
2 +
3 +Permission is hereby granted, free of charge,
4 +to any person obtaining a copy of this software and
5 +associated documentation files (the "Software"), to
6 +deal in the Software without restriction, including
7 +without limitation the rights to use, copy, modify,
8 +merge, publish, distribute, sublicense, and/or sell
9 +copies of the Software, and to permit persons to whom
10 +the Software is furnished to do so,
11 +subject to the following conditions:
12 +
13 +The above copyright notice and this permission notice
14 +shall be included in all copies or substantial portions of the Software.
15 +
16 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
20 +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.mdView
@@ -1,0 +1,31 @@
1 +# ssb-private-groups
2 +
3 +
4 +## receive key
5 +
6 +to indicate you support box2, post a message
7 +
8 +```
9 +{
10 + type: 'private-msg-key',
11 + key: curve25519.public
12 +}
13 +``
14 +when decrypting messages from another feed,
15 +you know it came from their most recent
16 +private message key, because they are in a strict order.
17 +
18 +when decrypting messages _for your feed_ these must
19 +be combined with each private-msg-key you retain.
20 +when rotating keys, it is advisable to keep both keys
21 +alive for an overlap period, to improve chances that
22 +sender has your new key when they write a message to you.
23 +
24 +If they write a message to a key you have discarded,
25 +you won't know, because you'll be unable to decrypt that message.
26 +
27 +
28 +
29 +## License
30 +
31 +MIT
index.jsView
@@ -1,0 +1,151 @@
1 +function 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 +
7 +function ctxtToBuffer(ctxt) {
8 + return isBox2(ctxt) && Buffer.from(ctxt.substring(ctxt.indexOf('.')), 'base64')
9 +}
10 +
11 +function 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 +
26 +function getGroupMsgKey(previous, group) {
27 + return hmac(Buffer.concat([previous, group.id]), group.unbox)
28 +}
29 +
30 +exports.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 +
package.jsonView
@@ -1,0 +1,19 @@
1 +{
2 + "name": "ssb-private-groups",
3 + "description": "",
4 + "version": "1.0.0",
5 + "homepage": "https://github.com/dominictarr/ssb-private-groups",
6 + "repository": {
7 + "type": "git",
8 + "url": "git://github.com/dominictarr/ssb-private-groups.git"
9 + },
10 + "dependencies": {
11 + },
12 + "devDependencies": {
13 + },
14 + "scripts": {
15 + "test": "set -e; for t in test/*.js; do node $t; done"
16 + },
17 + "author": "Dominic Tarr <dominic.tarr@gmail.com> (http://dominictarr.com)",
18 + "license": "MIT"
19 +}

Built with git-ssb-web