forked from ssbc/ssb-tribes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
envelope.js
94 lines (74 loc) · 3.28 KB
/
envelope.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/* eslint-disable camelcase */
const { isFeed, isCloakedMsg: isGroup } = require('ssb-ref')
const { box, unboxKey, unboxBody } = require('envelope-js')
const SecretKey = require('./lib/secret-key')
const { FeedId, MsgId } = require('./lib/cipherlinks')
function isEnvelope (ciphertext) {
return ciphertext.endsWith('.box2')
}
module.exports = function Envelope (keystore, state) {
function boxer (content, previousFeedState) {
if (content.recps.length > 16) {
throw new Error(`private-group spec allows maximum 16 slots, but you've tried to use ${content.recps.length}`)
}
// groupId can only be in first "slot"
if (!isGroup(content.recps[0]) && !isFeed(content.recps[0])) return null
// any subsequent slots are only for feedId
if (content.recps.length > 1 && !content.recps.slice(1).every(isFeed)) {
if (content.recps.slice(1).find(isGroup)) {
throw new Error('private-group spec only allows groupId in the first slot')
}
return null
}
const recipentKeys = content.recps.reduce((acc, recp) => {
if (isGroup(recp)) {
const keyInfo = keystore.group.get(recp)
if (!keyInfo) throw new Error(`unknown groupId ${recp}, cannot encrypt message`)
return [...acc, keyInfo]
}
// use a special key for your own feedId
if (recp === state.keys.id) {
return [...acc, keystore.ownKeys(recp)[0]]
}
return [...acc, keystore.author.sharedDMKey(recp)]
}, [])
const plaintext = Buffer.from(JSON.stringify(content), 'utf8')
const msgKey = new SecretKey().toBuffer()
const previousMessageId = new MsgId(previousFeedState.id).toTFK()
const envelope = box(plaintext, state.feedId, previousMessageId, msgKey, recipentKeys)
return envelope.toString('base64') + '.box2'
}
/* unboxer components */
function key (ciphertext, { author, previous }) {
if (!isEnvelope(ciphertext)) return null
const envelope = Buffer.from(ciphertext.replace('.box2', ''), 'base64')
const feed_id = new FeedId(author).toTFK()
const prev_msg_id = new MsgId(previous).toTFK()
const trial_group_keys = keystore.author.groupKeys(author)
const readKeyFromGroup = unboxKey(envelope, feed_id, prev_msg_id, trial_group_keys, { maxAttempts: 1 })
// NOTE the group recp is only allowed in the first slot,
// so we only test group keys in that slot (maxAttempts: 1)
if (readKeyFromGroup) return readKeyFromGroup
const trial_dm_keys = [
keystore.author.sharedDMKey(author),
...keystore.ownKeys()
]
return unboxKey(envelope, feed_id, prev_msg_id, trial_dm_keys, { maxAttempts: 16 })
// we then test all dm keys in up to 16 slots (maxAttempts: 16)
}
function value (ciphertext, { author, previous }, read_key) {
if (!isEnvelope(ciphertext)) return null
// TODO change unboxer signature to allow us to optionally pass variables
// from key() down here to save computation
const envelope = Buffer.from(ciphertext.replace('.box2', ''), 'base64')
const feed_id = new FeedId(author).toTFK()
const prev_msg_id = new MsgId(previous).toTFK()
const plaintext = unboxBody(envelope, feed_id, prev_msg_id, read_key)
if (!plaintext) return
return JSON.parse(plaintext.toString('utf8'))
}
return {
boxer,
unboxer: { key, value }
}
}