-
Notifications
You must be signed in to change notification settings - Fork 9
/
handshake.go
285 lines (256 loc) · 7.32 KB
/
handshake.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
package handshake
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"math/big"
"strings"
"golang.org/x/crypto/blake2b"
)
type role int
const (
defaultEntropyBytes = 96
// Version is the hard coded version of handshake-core running
Version = "0.0.1"
)
const (
initiator role = iota
peer
)
type handshake struct {
Role role
Negotiators []negotiator
Config handshakeConfig
Position negotiator
PeerTotal int
}
type handshakeConfig struct {
Version string
}
type negotiator struct {
Entropy []byte
Alias string
Strategy strategy
SortOrder int
}
type peerConfig struct {
Entropy string `json:"entropy"`
Alias string `json:"alias"`
Config strategyPeerConfig `json:"config"`
Item int `json:"item,omitempty"`
TotalItems int `json:"total_items,omitempty"`
}
// AddPeer takes a peerConfig and adds it to a handshake negotiator slice. It checks for unique Entropy bytes.
func (h *handshake) AddPeer(config peerConfig) error {
if h.Role == peer {
if config.Item == 0 {
return errors.New("missing sort order")
}
if config.Item > config.TotalItems {
return errors.New("sort oder id is greater than total size")
}
h.PeerTotal = config.TotalItems
}
n, err := newNegotiatorFromPeerConfig(config)
if err != nil {
return err
}
// ensure that the same peer isn't added twice
for _, negotiator := range h.Negotiators {
if bytes.Equal(negotiator.Entropy, n.Entropy) {
return errors.New("duplicate detected, peer must be unique")
}
}
h.Negotiators = append(h.Negotiators, n)
if h.Role == peer {
if config.Item == 1 && config.TotalItems == 2 {
h.Negotiators = append(h.Negotiators, h.Position)
h.Negotiators[1].SortOrder = 2
}
}
if h.Role == initiator {
h.PeerTotal = len(h.Negotiators)
}
return nil
}
// Share returns JSON encoded bytes of a peerConfig and an error
func (n negotiator) Share() (b []byte, err error) {
config, err := n.PeerConfig()
if err != nil {
return
}
b, err = json.Marshal(config)
if err != nil {
return
}
return
}
func (n negotiator) PeerConfig() (config peerConfig, err error) {
stratConfig, err := n.Strategy.Share()
if err != nil {
return
}
config = peerConfig{
Entropy: base64.StdEncoding.EncodeToString(n.Entropy),
Alias: n.Alias,
Config: stratConfig,
}
return
}
// AllPeersReceived checks the handshake state to see if PeerTotal and Negotiator counts match.
// True is only returned in the case that len(h.Negotiators) and h.PeerTotal are greater than 0 and their ints match.
func (h *handshake) AllPeersReceived() bool {
nCount := len(h.Negotiators)
if nCount == 0 {
return false
}
if h.PeerTotal == 0 {
return false
}
if nCount == h.PeerTotal {
return true
}
return false
}
// GetAllConfigs returns an array of all configs needed for a handshake and includes item counts.
func (h *handshake) GetAllConfigs() (configs []peerConfig, err error) {
// only the initiator should run this function
if h.Role != initiator {
return configs, errors.New("only an initiator can GetAllConfigs")
}
totalItems := len(h.Negotiators)
// check for at least two peers
if totalItems <= 1 {
return configs, errors.New("at least two peers must be present to ShareAll")
}
// check that Position and the first negotiator entropy bytes match
if !bytes.Equal(h.Position.Entropy, h.Negotiators[0].Entropy) {
return configs, errors.New("initiator sort order mismatch")
}
h.PeerTotal = totalItems
for i, n := range h.Negotiators {
itemNumber := i + 1
c, err := n.PeerConfig()
if err != nil {
return configs, err
}
c.Item = itemNumber
c.TotalItems = totalItems
configs = append(configs, c)
h.Negotiators[i].SortOrder = itemNumber
}
return configs, nil
}
func (h *handshake) SortedNegotiatorList() ([]negotiator, error) {
totalItems := len(h.Negotiators)
if totalItems <= 1 {
return []negotiator{}, errors.New("at least two peers must be present to sort the list")
}
negotiators := make([]negotiator, totalItems)
if totalItems != h.PeerTotal {
return []negotiator{}, errors.New("peerTotal and slice total mismatch")
}
for _, n := range h.Negotiators {
if (n.SortOrder < 1) || (n.SortOrder > totalItems) {
return []negotiator{}, errors.New("invalid sort order item")
}
negotiators[n.SortOrder-1] = n
}
for i, n := range negotiators {
if n.SortOrder != i+1 {
return []negotiator{}, errors.New("invalid sort validation")
}
}
return negotiators, nil
}
// generatePepper takes a list of negotiators and generates a blake2b-512 hash form the first 32 bytes of each entry set.
func generatePepper(negotiators []negotiator) []byte {
var b []byte
for _, n := range negotiators {
b = append(b, n.Entropy[:32]...)
}
h := blake2b.Sum512(b)
return h[:]
}
// GetPeerTotal returns the total count of peers expected for handshake exchange.
// If no peers are present, it returns 0, meaning peer count is invalid
// If 1 peer is present, it returns 1, for simplified exchange between two parties
// If 2 or more peers are present, it returns n + 1 as the total, to deal with sort order
func (h *handshake) GetPeerTotal() int {
return len(h.Negotiators)
}
type handshakeOptions struct {
Role role
Alias string
}
func genPosition() negotiator {
return negotiator{
Entropy: genRandBytes(defaultEntropyBytes),
Alias: genAlias(),
}
}
func newHandshake(strategy strategy, opts handshakeOptions) *handshake {
position := genPosition()
position.Strategy = strategy
if opts.Alias != "" {
position.Alias = opts.Alias
}
h := handshake{
Role: opts.Role,
Position: position,
Config: handshakeConfig{
Version: Version,
},
}
if h.Role == initiator {
h.Negotiators = append(h.Negotiators, position)
}
return &h
}
func newHandshakeInitiatorWithDefaults() *handshake {
opts := handshakeOptions{
Role: initiator,
}
return newHandshake(newDefaultStrategy(), opts)
}
func newHandshakePeerWithDefaults() *handshake {
opts := handshakeOptions{
Role: peer,
}
return newHandshake(newDefaultStrategy(), opts)
}
func genAlias() string {
var aliasSlice []string
npa := []string{
"alfa", "bravo", "charlie", "delta", "echo", "foxtrot", "golf",
"hotel", "india", "juliett", "kilo", "lima", "mike", "november",
"oscar", "papa", "quebec", "romeo", "sierra", "tango", "uniform",
"victor", "whiskey", "x-ray", "yankee", "zulu",
}
for i := 0; i < 3; i++ {
x, _ := rand.Int(rand.Reader, big.NewInt(int64(len(npa))))
aliasSlice = append(aliasSlice, npa[x.Int64()])
}
return strings.Join(aliasSlice, "-")
}
func newNegotiatorFromPeerConfig(config peerConfig) (n negotiator, err error) {
if n.Entropy, err = base64.StdEncoding.DecodeString(config.Entropy); err != nil {
return
}
if n.Strategy, err = strategyFromPeerConfig(config.Config); err != nil {
return
}
n.Alias = config.Alias
n.SortOrder = config.Item
return
}
// TODO:
// - Get Session to support new Handshake (and store state)
// - Get Session to be able to receive peer bytes and import it into the handshake
// - Initiator should stay open until a complete task has been processed
// - Initiator should return its config as well as the others (if more than 2, we won't do that initially)
// - Initiator should have a total code count in his scanner
// - Once peer has scanned all codes needed, both parties should generate preshared keys
// - this will initiate setting up the actual chat