-
Notifications
You must be signed in to change notification settings - Fork 12
/
oob.go
369 lines (315 loc) · 9.48 KB
/
oob.go
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
package rpc
import (
"bytes"
"compress/zlib"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"os"
"time"
"github.com/companyzero/bisonrelay/ratchet"
"github.com/companyzero/bisonrelay/sw"
"github.com/companyzero/bisonrelay/zkidentity"
"github.com/companyzero/sntrup4591761"
)
type InviteFunds struct {
Tx TxHash `json:"txid"`
Index uint32 `json:"index"`
Tree int8 `json:"tree"`
PrivateKey string `json:"private_key"`
HeightHint uint32 `json:"height_hint"`
Address string `json:"address"`
}
// OOBPublicIdentityInvite is an unencrypted OOB command which contains all
// information to kick of an initial KX. This command is NOT part of the wire
// protocol. This is provided out-of-band. With this the other side can
// commence a KX by issuing a RMOCHalfKX command to the provided
// InitialRendezvous.
type OOBPublicIdentityInvite struct {
Public zkidentity.PublicIdentity `json:"public"`
InitialRendezvous zkidentity.ShortID `json:"initialrendezvous"`
ResetRendezvous zkidentity.ShortID `json:"resetrendezvous"`
Funds *InviteFunds `json:"funds,omitempty"`
}
const OOBCPublicIdentityInvite = "oobpublicidentityinvite"
// CreateOOBPublicIdentityInvite returns a OOBPublicIdentityInvite structure
// with a random initial and reset rendezvous.
func CreateOOBPublicIdentityInvite(pi zkidentity.PublicIdentity) (*OOBPublicIdentityInvite, error) {
opii := OOBPublicIdentityInvite{
Public: pi,
}
_, err := io.ReadFull(rand.Reader, opii.InitialRendezvous[:])
if err != nil {
return nil, err
}
_, err = io.ReadFull(rand.Reader, opii.ResetRendezvous[:])
if err != nil {
return nil, err
}
return &opii, nil
}
// MarshalOOBPublicIdentityInvite returns a JSON encoded OOBPublicIdentityInvite.
func MarshalOOBPublicIdentityInvite(pii *OOBPublicIdentityInvite) ([]byte, error) {
return json.Marshal(pii)
}
func DecryptOOBPublicIdentityInvite(packed []byte, key *zkidentity.FixedSizeSntrupPrivateKey) (*OOBPublicIdentityInvite, error) {
pii, err := DecryptOOB(packed, key)
if err != nil {
return nil, err
}
p, ok := pii.(OOBPublicIdentityInvite)
if !ok {
return nil, fmt.Errorf("invalid type: %T", pii)
}
return &p, nil
}
// UnmarshalOOBPublicIdentityInviteFile returns an OOBPublicIdentityInvite from
// a file.
func UnmarshalOOBPublicIdentityInviteFile(filename string) (*OOBPublicIdentityInvite, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
jr := json.NewDecoder(f)
var pii OOBPublicIdentityInvite
err = jr.Decode(&pii)
if err != nil {
return nil, err
}
return &pii, nil
}
// NewHalfRatchetKX creates a new half ratchet between two identities. It returns
// the half ratchet and a random key exchange structure.
func NewHalfRatchetKX(us *zkidentity.FullIdentity, them zkidentity.PublicIdentity) (*ratchet.Ratchet, *ratchet.KeyExchange, error) {
// Create new ratchet with remote identity
r := ratchet.New(rand.Reader)
r.MyPrivateKey = &us.PrivateKey
r.TheirPublicKey = &them.Key
// Fill out half the kx
hkx := new(ratchet.KeyExchange)
err := r.FillKeyExchange(hkx)
if err != nil {
return nil, nil, err
}
return r, hkx, nil
}
// NewHalfKX creates a RMOHalfKX structure from a half ratchet.
func NewHalfKX(us *zkidentity.FullIdentity, hkx *ratchet.KeyExchange) (*RMOHalfKX, error) {
// Create half KX RPC
halfKX := RMOHalfKX{
Public: us.Public,
HalfKX: *hkx,
}
_, err := io.ReadFull(rand.Reader, halfKX.InitialRendezvous[:])
if err != nil {
return nil, err
}
_, err = io.ReadFull(rand.Reader, halfKX.ResetRendezvous[:])
if err != nil {
return nil, err
}
return &halfKX, nil
}
// NewFullRatchetKX creates a new full ratchet between two identities. It returns
// the completed full ratchet.
func NewFullRatchetKX(us *zkidentity.FullIdentity, them zkidentity.PublicIdentity, halfKX *ratchet.KeyExchange) (*ratchet.Ratchet, *ratchet.KeyExchange, error) {
// Fill out missing bits to create full ratchet
r := ratchet.New(rand.Reader)
r.MyPrivateKey = &us.PrivateKey
r.TheirPublicKey = &them.Key
fkx := new(ratchet.KeyExchange)
err := r.FillKeyExchange(fkx)
if err != nil {
return nil, nil, err
}
// Complete ratchet
err = r.CompleteKeyExchange(halfKX, false)
if err != nil {
return nil, nil, err
}
return r, fkx, nil
}
// NewFullKX creates a RMOFullKX structure from a full ratchet.
func NewFullKX(fkx *ratchet.KeyExchange) (*RMOFullKX, error) {
return &RMOFullKX{FullKX: *fkx}, nil
}
// EncryptRMO returns an encrypted blob from x.
// The returned blob is packed and prefixed with a sntrup ciphertext followed
// by an encrypted JSON objecti; or [sntrup ciphertext][encrypted JSON object].
func EncryptRMO(x interface{}, them zkidentity.PublicIdentity, zlibLevel int) ([]byte, error) {
// Create shared key that will be discarded as function exits
cipherText, sharedKey, err := sntrup4591761.Encapsulate(rand.Reader,
(*sntrup4591761.PublicKey)(&them.Key))
if err != nil {
return nil, err
}
defer func() {
for i := 0; i < len(sharedKey); i++ {
sharedKey[i] ^= sharedKey[i]
}
}()
blob, err := ComposeRMO(x, zlibLevel)
if err != nil {
return nil, err
}
blobEncrypted, err := sw.Seal(blob, sharedKey)
if err != nil {
return nil, err
}
packed := make([]byte, sntrup4591761.CiphertextSize+len(blobEncrypted))
copy(packed[0:], cipherText[:])
copy(packed[sntrup4591761.CiphertextSize:], blobEncrypted)
return packed, nil
}
func DecryptOOB(packed []byte, key *zkidentity.FixedSizeSntrupPrivateKey) (interface{}, error) {
if len(packed) < sntrup4591761.CiphertextSize {
return nil, fmt.Errorf("packed blob too small")
}
var cipherText sntrup4591761.Ciphertext
copy(cipherText[:], packed[0:sntrup4591761.CiphertextSize])
// Decrypt key and invite blob
sharedKey, n := sntrup4591761.Decapsulate(&cipherText, (*sntrup4591761.PrivateKey)(key))
if n != 1 {
return nil, fmt.Errorf("could not decapsulate")
}
hkxb, ok := sw.Open(packed[sntrup4591761.CiphertextSize:], sharedKey)
if !ok {
return nil, fmt.Errorf("could not open")
}
_, hkx, err := DecomposeRMO(hkxb)
if err != nil {
return nil, fmt.Errorf("DecomposeRMO %v", err)
}
return hkx, nil
}
// DecryptOOBHalfKXBlob decrypts a packed RMOHalfKX blob.
func DecryptOOBHalfKXBlob(packed []byte, key *zkidentity.FixedSizeSntrupPrivateKey) (*RMOHalfKX, error) {
hkx, err := DecryptOOB(packed, key)
if err != nil {
return nil, err
}
h, ok := hkx.(RMOHalfKX)
if !ok {
return nil, fmt.Errorf("invalid type: %T", hkx)
}
return &h, nil
}
// DecryptOOBFullKXBlob decrypts a packed RMOFullKX blob.
func DecryptOOBFullKXBlob(packed []byte, key *zkidentity.FixedSizeSntrupPrivateKey) (*RMOFullKX, error) {
fkx, err := DecryptOOB(packed, key)
if err != nil {
return nil, err
}
f, ok := fkx.(RMOFullKX)
if !ok {
return nil, fmt.Errorf("invalid type: %T", fkx)
}
return &f, nil
}
// Following is the portion of the oob protocol which does travel over the
// wire.
const (
RMOHeaderVersion = 1
)
// RMOHeader describes which command follows this structure.
type RMOHeader struct {
Version uint64 `json:"version"`
Timestamp int64 `json:"timestamp"`
Command string `json:"command"`
}
// RMOHalfKX is the command that flows after receiving an
// OOBPublicIdentityInvite.
type RMOHalfKX struct {
Public zkidentity.PublicIdentity `json:"public"`
HalfKX ratchet.KeyExchange `json:"halfkx"`
InitialRendezvous ratchet.RVPoint `json:"initialrendezvous"`
ResetRendezvous ratchet.RVPoint `json:"resetrendezvous"`
}
const RMOCHalfKX = "ohalfkx"
// RMOFullKX
type RMOFullKX struct {
FullKX ratchet.KeyExchange `json:"fullkx"`
}
const RMOCFullKX = "ofullkx"
// XXX see if we can combine this with the regular code path (ComposeRM)
// ComposeRMO creates a blobified oob message that has a header and a
// payload that can then be encrypted and transmitted to the other side.
func ComposeRMO(rm interface{}, zlibLevel int) ([]byte, error) {
h := RMOHeader{
Version: RMOHeaderVersion,
Timestamp: time.Now().Unix(),
}
switch rm.(type) {
case OOBPublicIdentityInvite:
h.Command = OOBCPublicIdentityInvite
case RMOHalfKX:
h.Command = RMOCHalfKX
case RMOFullKX:
h.Command = RMOCFullKX
default:
return nil, fmt.Errorf("unknown oob routed message "+
"type: %T", rm)
}
// Create header, note that the encoder appends a '\n'
mb := &bytes.Buffer{}
w, err := zlib.NewWriterLevel(mb, zlibLevel)
if err != nil {
return nil, err
}
e := json.NewEncoder(w)
err = e.Encode(h)
if err != nil {
return nil, err
}
// Append payload
err = e.Encode(rm)
if err != nil {
return nil, err
}
err = w.Close()
if err != nil {
return nil, err
}
return mb.Bytes(), nil
}
func DecomposeRMO(mb []byte) (*RMOHeader, interface{}, error) {
cr, err := zlib.NewReader(bytes.NewReader(mb))
if err != nil {
return nil, nil, err
}
lr := &limitedReader{R: cr, N: maxRMDecompressSize}
// Read header
var h RMOHeader
d := json.NewDecoder(lr)
err = d.Decode(&h)
if err != nil {
return nil, nil, fmt.Errorf("Decode %v", err)
}
// Decode payload
pmd := d
var payload interface{}
switch h.Command {
case OOBCPublicIdentityInvite:
var pii OOBPublicIdentityInvite
err = pmd.Decode(&pii)
payload = pii
case RMOCHalfKX:
var hkx RMOHalfKX
err = pmd.Decode(&hkx)
payload = hkx
case RMOCFullKX:
var fkx RMOFullKX
err = pmd.Decode(&fkx)
payload = fkx
default:
return nil, nil, fmt.Errorf("unknown oob "+
"message command: %v", h.Command)
}
if err != nil {
return nil, nil, fmt.Errorf("decode command %v: %w",
h.Command, err)
}
return &h, payload, nil
}