forked from IBM/fluent-forward-go
/
handshake.go
213 lines (177 loc) · 6.1 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
/*
MIT License
Copyright contributors to the fluent-forward-go project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package protocol
import (
"crypto/sha512"
"encoding/hex"
"errors"
"io"
)
//go:generate msgp
// =========
// HANDSHAKE
// =========
const (
MsgTypeHelo = "HELO"
MsgTypePing = "PING"
MsgTypePong = "PONG"
)
// Remember that the handshake flow is like this:
// 1. Server -> HELO -> Client
// 2. Client -> PING -> Server
// 3. Server -> PONG -> Client
// NewHelo returns a Helo message with the specified options.
// if opts is nil, then a nonce is generated, auth is left empty, and
// keepalive is true.
func NewHelo(opts *HeloOpts) *Helo {
h := Helo{MessageType: MsgTypeHelo}
if opts == nil {
h.Options = &HeloOpts{
Keepalive: true,
}
} else {
h.Options = opts
}
return &h
}
// Helo is the initial handshake message, sent by the server and received
// by the client. Client will respond with a Ping.
//
//msgp:tuple Helo
type Helo struct {
MessageType string
Options *HeloOpts
}
type HeloOpts struct {
Nonce []byte `msg:"nonce"`
Auth []byte `msg:"auth"`
Keepalive bool `msg:"keepalive"`
}
// NewPing returns a PING message. The digest is computed
// from the hostname, key, salt, and nonce using SHA512.
func NewPing(hostname string, sharedKey, salt, nonce []byte) (*Ping, error) {
return makePing(hostname, sharedKey, salt, nonce)
}
// NewPingWithAuth returns a PING message containing the username and password
// to be used for authentication. The digest is computed
// from the hostname, key, salt, and nonce using SHA512.
func NewPingWithAuth(hostname string, sharedKey, salt, nonce []byte, username, password string) (*Ping, error) {
return makePing(hostname, sharedKey, salt, nonce, username, password)
}
func makePing(hostname string, sharedKey, salt, nonce []byte, creds ...string) (*Ping, error) {
hexDigest, err := computeHexDigest(salt, hostname, nonce, sharedKey)
p := Ping{
MessageType: MsgTypePing,
ClientHostname: hostname,
SharedKeySalt: salt,
SharedKeyHexDigest: hexDigest,
}
if len(creds) >= 2 {
p.Username = creds[0]
p.Password = creds[1]
}
return &p, err
}
// Ping is the response message sent by the client after receiving a
// Helo from the server. Server will respond with a Pong.
//
//msgp:tuple Ping
type Ping struct {
MessageType string
ClientHostname string
SharedKeySalt []byte
SharedKeyHexDigest string
Username string
Password string
}
// NewPong returns a PONG message. AuthResult indicates
// whether the credentials presented by the client were accepted and therefore
// whether the client can continue using the connection, switching from
// handshake mode to sending events.
// As with the PING, the digest is computed
// from the hostname, key, salt, and nonce using SHA512.
// Server implementations must use the nonce created for the initial Helo and
// the salt sent by the client in the Ping.
func NewPong(authResult bool, reason string, hostname string, sharedKey []byte,
helo *Helo, ping *Ping) (*Pong, error) {
if helo == nil || ping == nil {
return nil, errors.New("Either helo or ping is nil")
}
if helo.Options == nil {
return nil, errors.New("Helo has a nil options field")
}
hexDigest, err := computeHexDigest(ping.SharedKeySalt, hostname, helo.Options.Nonce, sharedKey)
p := Pong{
MessageType: MsgTypePong,
AuthResult: authResult,
Reason: reason,
ServerHostname: hostname,
SharedKeyHexDigest: hexDigest,
}
return &p, err
}
// Pong is the response message sent by the server after receiving a
// Ping from the client. A Pong concludes the handshake.
//
//msgp:tuple Pong
type Pong struct {
MessageType string
AuthResult bool
Reason string
ServerHostname string
SharedKeyHexDigest string
}
// ValidatePingDigest validates that the digest contained in the PING message
// is valid for the client hostname (as contained in the PING).
// Returns a non-nil error if validation fails, nil otherwise.
func ValidatePingDigest(p *Ping, key, nonce []byte) error {
return validateDigest(p.SharedKeyHexDigest, key, nonce, p.SharedKeySalt, p.ClientHostname)
}
// ValidatePongDigest validates that the digest contained in the PONG message
// is valid for the server hostname (as contained in the PONG).
// Returns a non-nil error if validation fails, nil otherwise.
func ValidatePongDigest(p *Pong, key, nonce, salt []byte) error {
return validateDigest(p.SharedKeyHexDigest, key, nonce, salt, p.ServerHostname)
}
func validateDigest(received string, key, nonce, salt []byte, hostname string) error {
expected, err := computeHexDigest(salt, hostname, nonce, key)
if err != nil {
return err
}
if received != expected {
return errors.New("No match")
}
return nil
}
func computeHexDigest(salt []byte, hostname string, nonce, sharedKey []byte) (string, error) {
h := sha512.New()
h.Write(salt)
_, err := io.WriteString(h, hostname)
if err != nil {
return "", err
}
h.Write(nonce)
h.Write(sharedKey)
sum := h.Sum(nil)
hexOut := make([]byte, hex.EncodedLen(len(sum)))
hex.Encode(hexOut, sum)
stringValue := string(hexOut[:])
return stringValue, err
}