Files: 0e94992aa8bedac43906183f616bff699ec2484b / valid.js
5693 bytesRaw
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 = 'peer-invites:'+c |
9 | return err |
10 | } |
11 | |
12 | function isObject (o) { |
13 | return o && 'object' === typeof o |
14 | } |
15 | |
16 | function toBuffer(str) { |
17 | return Buffer.isBuffer(str) ? str : Buffer.from(str, 'base64') |
18 | } |
19 | |
20 | //this should really be refactored out somewhere. |
21 | function toMsgId(msg) { |
22 | return '%'+ssbKeys.hash(JSON.stringify(msg, null, 2)) |
23 | } |
24 | |
25 | //derive key for private field |
26 | function hash (seed) { |
27 | if(!Buffer.isBuffer(seed)) throw new Error('expected seed as buffer') |
28 | return u.hash(seed) |
29 | } |
30 | |
31 | //derive key for reveal field |
32 | function hash2 (seed) { |
33 | if(!Buffer.isBuffer(seed)) throw new Error('expected seed as buffer') |
34 | return u.hash(u.hash(seed)) |
35 | } |
36 | |
37 | |
38 | exports.createInvite = function (seed, host, reveal, private, caps) { |
39 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
40 | |
41 | seed = toBuffer(seed) |
42 | var keys = ssbKeys.generate(null, seed) //K |
43 | if(keys.id === host) |
44 | throw code(new Error('do not create invite with own public key'), 'peer-invites:no-own-goal') |
45 | return ssbKeys.signObj(keys, caps.peerInvite, { |
46 | type: 'peer-invite', |
47 | invite: keys.id, |
48 | host: host, //sign our own key, to prove we created K |
49 | reveal: reveal ? u.box(reveal, hash2(seed)) : undefined, |
50 | private: private ? u.box(private, hash(seed)) : undefined |
51 | }) |
52 | } |
53 | |
54 | exports.verifyInvitePublic = function (msg, caps) { |
55 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
56 | |
57 | if(msg.content.host != msg.author) |
58 | throw code(new Error('host did not match author'), 'host-must-match-author') |
59 | |
60 | if(!ssbKeys.verifyObj(msg.content.invite, caps.peerInvite, msg.content)) |
61 | throw code(new Error('invalid invite signature'), 'invite-signature-failed') |
62 | |
63 | //an ordinary message so doesn't use special hmac_key, unless configed to. |
64 | if(!ssbKeys.verifyObj(msg.author, caps.sign, msg)) |
65 | throw code(new Error('invalid host signature'), 'host-signature-failed') |
66 | return true |
67 | } |
68 | |
69 | exports.verifyInvitePrivate = function (msg, seed, caps) { |
70 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
71 | |
72 | seed = toBuffer(seed) |
73 | exports.verifyInvitePublic(msg, caps) |
74 | if(msg.content.reveal) { |
75 | var reveal = u.unbox(msg.content.reveal, hash2(seed)) |
76 | if(!reveal) throw code(new Error('could not decrypt reveal field'), 'decrypt-reveal-failed') |
77 | } |
78 | if(msg.content.private) { |
79 | var private = u.unbox(msg.content.private, hash(seed)) |
80 | if(!private) throw code(new Error('could not decrypt private field'), 'decrypt-private-failed') |
81 | } |
82 | |
83 | return {reveal: reveal, private: private} |
84 | } |
85 | |
86 | exports.createAccept = function (msg, seed, id, caps) { |
87 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
88 | |
89 | seed = toBuffer(seed) |
90 | exports.verifyInvitePrivate(msg, seed, caps) |
91 | var keys = ssbKeys.generate(null, seed) //K |
92 | if(keys.id != msg.content.invite) |
93 | throw code(new Error('seed does not match invite'), 'seed-must-match-invite') |
94 | var inviteId = toMsgId(msg) |
95 | var content = { |
96 | type: 'peer-invite/accept', |
97 | receipt: inviteId, |
98 | id: id |
99 | } |
100 | if(msg.content.reveal) |
101 | content.key = hash2(seed).toString('base64') |
102 | return ssbKeys.signObj(keys, caps.peerInvite, content) |
103 | } |
104 | |
105 | exports.verifyAcceptOnly = function (accept, caps) { |
106 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
107 | if(accept.content.type !== 'peer-invite/accept') |
108 | throw code(new Error('accept must be type: "peer-invite/accept", was:'+JSON.stringify(accept.content.type)), 'accept-message-type') |
109 | if(!isMsg(accept.content.receipt)) |
110 | throw code(new Error('accept must reference invite message id'), 'accept-reference-invite') |
111 | //verify signed as ordinary message. |
112 | if(!ssbKeys.verifyObj(accept.content.id, caps.sign, accept)) |
113 | throw code(new Error('acceptance must be signed by claimed key'), 'accept-signature-failed') |
114 | } |
115 | |
116 | exports.verifyAccept = function (accept, invite_msg, caps) { |
117 | if(!isObject(caps)) throw new Error('caps *must* be provided') |
118 | if(!invite_msg) throw new Error('invite must be provided') |
119 | |
120 | exports.verifyAcceptOnly(accept, caps) |
121 | |
122 | if(invite_msg.content.type !== 'peer-invite') |
123 | throw code(new Error('accept must be type: invite, was:'+accept.content.type), 'peer-invites:invite-message-type') |
124 | |
125 | var invite_id = toMsgId(invite_msg) |
126 | var reveal |
127 | |
128 | if(invite_id !== accept.content.receipt) |
129 | throw code(new Error('acceptance not matched to given invite, got:'+invite_id+' expected:'+accept.content.receipt), 'accept-wrong-invite') |
130 | |
131 | if(accept.author === invite_msg.content.id) |
132 | throw code(new Error('guest must use a new key, not the same seed'), 'guest-key-reuse') |
133 | if(invite_msg.content.reveal) { |
134 | if(!accept.content.key) |
135 | throw code(new Error('accept missing reveal key, when invite has it'), 'accept-must-reveal-key') |
136 | reveal = u.unbox(invite_msg.content.reveal, toBuffer(accept.content.key)) |
137 | if(!reveal) throw code(new Error('accept did not correctly reveal invite'), 'decrypt-accept-reveal-failed') |
138 | } |
139 | |
140 | if(!ssbKeys.verifyObj(invite_msg.content.invite, caps.peerInvite, accept.content)) |
141 | throw code(new Error('did not verify invite-acceptance contents'), 'accept-invite-signature-failed') |
142 | //an ordinary message, so does not use hmac_key |
143 | return reveal || true |
144 | } |
145 | |
146 | exports.createConfirm = function (accept) { |
147 | return { |
148 | type: 'peer-invite/confirm', |
149 | embed: accept, |
150 | //second pointer back to receipt, so that links can find it |
151 | //(since it unfortunately does not handle links nested deeper |
152 | //inside objects. when we look up the message, |
153 | //confirm that content.embed.content.receipt is the same) |
154 | receipt: accept.content.receipt |
155 | } |
156 | } |
157 | |
158 |
Built with git-ssb-web