git ssb

0+

Dominic / ssb-peer-invites



Commit 6982758b9d958f06b1b22dec3bc4dd2017f90441

implement flumeview and methods to accept an invite

Dominic Tarr committed on 4/3/2018, 4:50:19 AM
Parent: e52b3cbbebf89e82592eb4630f1327db640bed53

Files changed

index.jschanged
valid.jsadded
index.jsView
@@ -1,107 +1,161 @@
1-var ssbKeys = require('ssb-keys')
1 +var I = require('./valid')
22
3-var u = require('./util')
3 +/*
4 +uxer (someone who observes an invite, but not directly involved):
45
5-var invite_key = require('./cap')
6 +if we see
7 + someone post an invite I
8 + someone else post a confirmation C that I has been accepted A
9 + // OOO that A
10 + emded that A inside a C(A)
11 + match valid I->A's and interpret them like follows.
612
7-function code(err, c) {
8- err.code = 'user-invites:'+c
9- return err
10-}
13 + we only care about confirmations if it's of an invite we follow,
14 + and it hasn't been confirmed already.
1115
12-exports.createInvite = function (seed, host, reveal, private) {
13- var keys = ssbKeys.generate(null, seed) //K
14- if(keys.id === host)
15- throw code(new Error('do not create invite with own public key'), 'user-invites:no-own-goal')
16- return ssbKeys.signObj(keys, invite_key, {
17- type: 'invite',
18- invite: keys.id,
19- host: host, //sign our own key, to prove we created K
20- reveal: reveal ? u.box(reveal, u.hash(u.hash(seed))) : undefined,
21- private: private ? u.box(private, u.hash(seed)) : undefined
22- })
23-}
16 +{
17 + invited: {
18 + <alice>: { <bob>: true, ...}
19 + }
2420
25-exports.verifyInvitePublic = function (msg) {
26- if(msg.content.host != msg.author)
27- throw code(new Error('host did not match author'), 'host-must-match-author')
21 + invites: { <invites>, ...}
22 + accepts: { <accepts>, ...}
2823
29- if(!ssbKeys.verifyObj(msg.content.invite, invite_key, msg.content))
30- throw code(new Error('invalid invite signature'), 'invite-signature-failed')
31-
32- //an ordinary message so doesn't use special hmac_key
33- if(!ssbKeys.verifyObj(msg.author, msg))
34- throw code(new Error('invalid host signature'), 'host-signature-failed')
35- return true
3624 }
3725
38-exports.verifyInvitePrivate = function (msg, seed) {
39- exports.verifyInvitePublic(msg)
40- if(msg.content.reveal) {
41- var reveal = u.unbox(msg.content.reveal, u.hash(u.hash(seed)))
42- if(!reveal) throw code(new Error('could not decrypt reveal field'), 'decrypt-reveal-failed')
43- }
44- if(msg.content.private) {
45- var private = u.unbox(msg.content.private, u.hash(seed))
46- if(!private) throw code(new Error('could not decrypt private field'), 'decrypt-private-failed')
47- }
26 +---
4827
49- return {reveal: reveal, private: private}
50-}
28 +pub
5129
52-exports.createAccept = function (msg, seed, id) {
53- exports.verifyInvitePrivate(msg, seed)
54- var keys = ssbKeys.generate(null, seed) //K
55- if(keys.id != msg.content.invite)
56- throw code(new Error('seed does not match invite'), 'seed-must-match-invite')
57- var inviteId = '%'+ssbKeys.hash(JSON.stringify(msg, null, 2))
58- return ssbKeys.signObj(keys, invite_key, {
59- type: 'invite/accept',
60- receipt: inviteId,
61- id: id,
62- key: msg.content.reveal ? u.hash(u.hash(seed)).toString('base64') : undefined
63- })
64-}
30 + someone connects, using key from an open invite I
31 + they request that invite I (by it's id)
32 + they send a message accepting A the invite.
33 + the pub then posts confirmation (I,A)
6534
66-exports.verifyAccept = function (accept, invite) {
67- if(!invite) throw new Error('invite must be provided')
35 +*/
6836
69- if(accept.content.type !== 'invite/accept')
70- throw code(new Error('accept must be type: "invite/accept", was:'+JSON.stringify(accept.content.type)), 'user-invites:accept-message-type')
71- if(invite.content.type !== 'invite')
72- throw code(new Error('accept must be type: invite, was:'+accept.content.type), 'user-invites:invite-message-type')
37 +exports.name = 'invites'
7338
74- var invite_id = '%'+ssbKeys.hash(JSON.stringify(invite, null, 2))
75- var reveal
39 +exports.version = '1.0.0'
40 +exports.manifest = {
7641
77- if(invite_id !== accept.content.receipt)
78- throw code(new Error('acceptance not matched to given invite, got:'+invite_id+' expected:'+accept.content.receipt), 'accept-wrong-invite')
79-
80- if(accept.author === invite.content.id)
81- throw code(new Error('invitee must use a new key, not the same seed'), 'guest-key-reuse')
82- if(invite.content.reveal) {
83- if(!accept.content.key)
84- throw code(new Error('accept missing reveal key, when invite has it'), 'accept-must-reveal-key')
85- reveal = u.unbox(invite.content.reveal, new Buffer(accept.content.key, 'base64'))
86- if(!reveal) throw code(new Error('accept did not correctly reveal invite'), 'decrypt-accept-reveal-failed')
87- }
88-
89- if(!ssbKeys.verifyObj(invite.content.invite, invite_key, accept.content))
90- throw code(new Error('did not verify invite-acceptance contents'), 'accept-invite-signature-failed')
91- //an ordinary message, so does not use hmac_key
92- if(!ssbKeys.verifyObj(accept.content.id, accept))
93- throw code(new Error('acceptance must be signed by claimed key'), 'accept-signature-failed')
94- return reveal || true
9542 }
9643
44 +// KNOWN BUG: it's possible to accept an invite more than once,
45 +// but peers will ignore subsequent acceptances. it's possible
46 +// that this could create confusion in certain situations.
47 +// (but you'd get a feed that some peers thought was invited by Alice
48 +// other peers would think a different feed accepted that invite)
49 +// I guess the answer is to let alice reject the second invite?)
50 +// that would be easier to do if this was a levelreduce? (keys: reduce, instead of a single reduce?)
51 +exports.init = function (sbot, config) {
9752
53 + var index = sbot._flumeUse('invites', Reduce(1, function (acc, data) {
54 + if(!acc) acc = {invited: {}, invites:{}, accepts: {}}
9855
56 + var msg = data.value
57 + var invite, accept
58 + if(msg.content.type === 'invite') {
59 + invite = msg
60 + accept = acc.accepts[data.key]
61 + }
62 + else if(msg.content.type === 'invite/accept') {
63 + accept = msg
64 + invite = acc.invites[accept.content.receipt]
65 + }
66 + else if(msg.content.type === 'invite/confirm') {
67 + accept = msg.content.embed
68 + invite = acc.invites[accept.content.receipt]
69 + }
70 + if(invite && accept) {
71 + if(invite === true)
72 + return acc
73 + try {
74 + I.validateAccept(accept, invite)
75 + //delete matched invites, but _only_ if they are valid.
76 + delete acc.accepts[accept.receipt]
77 + //but remember that this invite has been processed.
78 + acc.invites[accept.receipt] = true
79 + } catch (err) {
80 + return acc //? or store something?
81 + }
82 + }
83 + else if(invite)
84 + acc.invites[data.key] = invite
85 + else if(accept)
86 + acc.accepts[accept.receipt] = accept
9987
88 + return acc
10089
90 + }))
10191
92 + sbot.auth.hook(function (fn, args) {
93 + var id = args[0], cb = args[1]
94 + index.get(function (err, v) {
95 + if(err) return cb(err)
96 + for(var k in v.invites)
97 + if(v.invites[k].invite === id)
98 + return cb(null, {
99 + allow: ['invite.getInvite', 'invites.accept'],
100 + deny: null
101 + })
102 + })
103 + })
102104
105 + //first
106 + invites.getInvite = function (invite_id, cb) {
107 + var self = this
108 + invites.get(function (err, v) {
109 + var invite = v.invites[invite_id]
110 + if(err) return cb(err)
111 + if(!invite)
112 + cb(code(
113 + new Error('unknown invite:'+invite_id),
114 + 'unknown-invite'
115 + ))
116 + else if(invite === true)
117 + cb(code(
118 + new Error('invite already used:'+invite_id),
119 + 'invite-already-used'
120 + ))
121 + //only allow the guest to request their own invite.
122 + else if(self.id !== invite.content.invite)
123 + cb(code(
124 + new Error('invite did not match client id'),
125 + 'invite-mismatch'
126 + ))
127 + else
128 + cb(null, v.invites[invite_id])
129 + })
130 + }
103131
132 + var accepted = {}
104133
134 + invites.accept = function (accept, cb) {
135 + //check if the invite in question hasn't already been accepted.
136 + invites.get(function (err, v) {
137 + var invite_id = accept.content.receipt
138 + var invite = v.invites[invite_id]
139 + if(invite === true || accepted[invite_id])
140 + return cb(code(
141 + new Error('invite already used:'+invite_id),
142 + 'invite-already-used'
143 + ))
144 + try {
145 + I.validateAccept(accept, invite)
146 + } catch (err) {
147 + return cb(err)
148 + }
149 + //there is a little race condition here
150 + accepted[invite_id] = true
151 + sbot.publish({type: 'invite/confirm', embed: accept}, function (err, msg) {
152 + delete accepted[invite_id]
153 + cb(err, msg)
154 + })
155 + })
156 + }
105157
158 + return invites
106159
160 +}
107161
valid.jsView
@@ -1,0 +1,103 @@
1 +var ssbKeys = require('ssb-keys')
2 +var isMsg = require('ssb-ref').isMsg
3 +var u = require('./util')
4 +
5 +var invite_key = require('./cap')
6 +
7 +function code(err, c) {
8 + err.code = 'user-invites:'+c
9 + return err
10 +}
11 +
12 +exports.createInvite = function (seed, host, reveal, private) {
13 + var keys = ssbKeys.generate(null, seed) //K
14 + if(keys.id === host)
15 + throw code(new Error('do not create invite with own public key'), 'user-invites:no-own-goal')
16 + return ssbKeys.signObj(keys, invite_key, {
17 + type: 'invite',
18 + invite: keys.id,
19 + host: host, //sign our own key, to prove we created K
20 + reveal: reveal ? u.box(reveal, u.hash(u.hash(seed))) : undefined,
21 + private: private ? u.box(private, u.hash(seed)) : undefined
22 + })
23 +}
24 +
25 +exports.verifyInvitePublic = function (msg) {
26 + if(msg.content.host != msg.author)
27 + throw code(new Error('host did not match author'), 'host-must-match-author')
28 +
29 + if(!ssbKeys.verifyObj(msg.content.invite, invite_key, msg.content))
30 + throw code(new Error('invalid invite signature'), 'invite-signature-failed')
31 +
32 + //an ordinary message so doesn't use special hmac_key
33 + if(!ssbKeys.verifyObj(msg.author, msg))
34 + throw code(new Error('invalid host signature'), 'host-signature-failed')
35 + return true
36 +}
37 +
38 +exports.verifyInvitePrivate = function (msg, seed) {
39 + exports.verifyInvitePublic(msg)
40 + if(msg.content.reveal) {
41 + var reveal = u.unbox(msg.content.reveal, u.hash(u.hash(seed)))
42 + if(!reveal) throw code(new Error('could not decrypt reveal field'), 'decrypt-reveal-failed')
43 + }
44 + if(msg.content.private) {
45 + var private = u.unbox(msg.content.private, u.hash(seed))
46 + if(!private) throw code(new Error('could not decrypt private field'), 'decrypt-private-failed')
47 + }
48 +
49 + return {reveal: reveal, private: private}
50 +}
51 +
52 +exports.createAccept = function (msg, seed, id) {
53 + exports.verifyInvitePrivate(msg, seed)
54 + var keys = ssbKeys.generate(null, seed) //K
55 + if(keys.id != msg.content.invite)
56 + throw code(new Error('seed does not match invite'), 'seed-must-match-invite')
57 + var inviteId = '%'+ssbKeys.hash(JSON.stringify(msg, null, 2))
58 + return ssbKeys.signObj(keys, invite_key, {
59 + type: 'invite/accept',
60 + receipt: inviteId,
61 + id: id,
62 + key: msg.content.reveal ? u.hash(u.hash(seed)).toString('base64') : undefined
63 + })
64 +}
65 +
66 +exports.verifyAcceptOnly = function (accept) {
67 + if(accept.content.type !== 'invite/accept')
68 + throw code(new Error('accept must be type: "invite/accept", was:'+JSON.stringify(accept.content.type)), 'accept-message-type')
69 + if(!isMsg(accept.content.receipt))
70 + throw code(new Error('accept must reference invite message id'), 'accept-reference-invite')
71 + if(!ssbKeys.verifyObj(accept.content.id, accept))
72 + throw code(new Error('acceptance must be signed by claimed key'), 'accept-signature-failed')
73 +}
74 +
75 +exports.verifyAccept = function (accept, invite) {
76 + if(!invite) throw new Error('invite must be provided')
77 +
78 + exports.verifyAcceptOnly(accept)
79 +
80 + if(invite.content.type !== 'invite')
81 + throw code(new Error('accept must be type: invite, was:'+accept.content.type), 'user-invites:invite-message-type')
82 +
83 + var invite_id = '%'+ssbKeys.hash(JSON.stringify(invite, null, 2))
84 + var reveal
85 +
86 + if(invite_id !== accept.content.receipt)
87 + throw code(new Error('acceptance not matched to given invite, got:'+invite_id+' expected:'+accept.content.receipt), 'accept-wrong-invite')
88 +
89 + if(accept.author === invite.content.id)
90 + throw code(new Error('invitee must use a new key, not the same seed'), 'guest-key-reuse')
91 + if(invite.content.reveal) {
92 + if(!accept.content.key)
93 + throw code(new Error('accept missing reveal key, when invite has it'), 'accept-must-reveal-key')
94 + reveal = u.unbox(invite.content.reveal, new Buffer(accept.content.key, 'base64'))
95 + if(!reveal) throw code(new Error('accept did not correctly reveal invite'), 'decrypt-accept-reveal-failed')
96 + }
97 +
98 + if(!ssbKeys.verifyObj(invite.content.invite, invite_key, accept.content))
99 + throw code(new Error('did not verify invite-acceptance contents'), 'accept-invite-signature-failed')
100 + //an ordinary message, so does not use hmac_key
101 + return reveal || true
102 +}
103 +

Built with git-ssb-web