-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathapis.go
383 lines (332 loc) · 11.4 KB
/
apis.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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
package libdisco
// This file implements a net.Conn interface over Disco.
// Most of this code was either taken directly or inspired from Go's crypto/tls package.
import (
"crypto"
"crypto/rand"
"encoding/hex"
"errors"
"io/ioutil"
"net"
"time"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/argon2"
)
// Server returns a new Disco server side connection
// using net.Conn as the underlying transport.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Server(conn net.Conn, config *Config) *Conn {
return &Conn{conn: conn, config: config}
}
// Client returns a new Disco client side connection
// using conn as the underlying transport.
// The config cannot be nil: users must set either ServerName or
// InsecureSkipVerify in the config.
func Client(conn net.Conn, config *Config) *Conn {
return &Conn{conn: conn, config: config, isClient: true}
}
// Listener implements a network listener (net.Listener) for Disco connections.
type Listener struct {
net.Listener
config *Config
}
// Accept waits for and returns the next incoming Disco connection.
// The returned connection is of type *Conn.
func (l *Listener) Accept() (net.Conn, error) {
c, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return Server(c, l.config), nil
}
// AcceptDisco waits for and returns the next incoming Disco connection.
// The returned connection is of type *Conn.
func (l *Listener) AcceptDisco() (*Conn, error) {
c, err := l.Listener.Accept()
if err != nil {
return nil, err
}
conn := Server(c, l.config)
return conn, nil
}
// Listen creates a Disco listener accepting connections on the
// given network address using net.Listen.
// The configuration config must be non-nil.
func Listen(network, laddr string, config *Config) (net.Listener, error) {
// check Config
if config == nil {
return nil, errors.New("Disco: no Config set")
}
if err := checkRequirements(false, config); err != nil {
panic(err)
}
// make net.Conn listen
l, err := net.Listen(network, laddr)
if err != nil {
return nil, err
}
// create new libdisco.listener
discoListener := new(Listener)
discoListener.Listener = l
discoListener.config = config
return discoListener, nil
}
// ListenDisco creates a Disco listener accepting connections on the
// given network address using net.Listen.
// The configuration config must be non-nil.
func ListenDisco(network, laddr string, config *Config) (*Listener, error) {
// check Config
if config == nil {
return nil, errors.New("Disco: no Config set")
}
if err := checkRequirements(false, config); err != nil {
panic(err)
}
// make net.Conn listen
l, err := net.Listen(network, laddr)
if err != nil {
return nil, err
}
// create new libdisco.listener
discoListener := new(Listener)
discoListener.Listener = l
discoListener.config = config
return discoListener, nil
}
type timeoutError struct{}
func (timeoutError) Error() string { return "Disco: DialWithDialer timed out" }
func (timeoutError) Timeout() bool { return true }
func (timeoutError) Temporary() bool { return true }
// this functions checks if at some point in the protocol
// the peer needs to verify the other peer static public key
// and if the peer needs to provide a proof for its static public key
var errNoPubkeyVerifier = errors.New("Disco: no public key verifier set in Config")
var errNoProof = errors.New("Disco: no public key proof set in Config")
func checkRequirements(isClient bool, config *Config) (err error) {
ht := config.HandshakePattern
if ht == NoiseNX || ht == NoiseKX || ht == NoiseXX || ht == NoiseIX {
if isClient && config.PublicKeyVerifier == nil {
return errNoPubkeyVerifier
} else if !isClient && config.StaticPublicKeyProof == nil {
return errNoProof
}
}
if ht == NoiseXN || ht == NoiseXK || ht == NoiseXX || ht == NoiseX || ht == NoiseIN || ht == NoiseIK || ht == NoiseIX {
if isClient && config.StaticPublicKeyProof == nil {
return errNoProof
} else if !isClient && config.PublicKeyVerifier == nil {
return errNoPubkeyVerifier
}
}
if ht == NoiseNNpsk2 && len(config.PreSharedKey) != 32 {
return errors.New("noise: a 32-byte pre-shared key needs to be passed as noise.Config")
}
return nil
}
// DialWithDialer connects to the given network address using dialer.Dial and
// then initiates a Disco handshake, returning the resulting Disco connection. Any
// timeout or deadline given in the dialer apply to connection and Disco
// handshake as a whole.
//
// DialWithDialer interprets a nil configuration as equivalent to the zero
// configuration; see the documentation of Config for the defaults.
// TODO: make sure sane defaults for time outs are set!!!
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (net.Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and Disco handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := time.Until(dialer.Deadline)
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
// check Config
if config == nil {
panic("Disco: no Config set")
}
if err := checkRequirements(true, config); err != nil {
panic(err)
}
// Dial the net.Conn first
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- timeoutError{}
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
// TODO: use the following code to implement some sort of SNI extension?
/*
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
*/
// Create the libdisco.Conn
conn := Client(rawConn, config)
// Do the handshake
if timeout == 0 {
err = conn.Handshake()
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
}
if err != nil {
rawConn.Close()
return nil, err
}
return conn, nil
}
// Dial connects to the given network address using net.Dial
// and then initiates a Disco handshake, returning the resulting
// Disco connection.
// Dial interprets a nil configuration as equivalent to
// the zero configuration; see the documentation of Config
// for the defaults.
func Dial(network, addr string, config *Config) (net.Conn, error) {
return DialWithDialer(new(net.Dialer), network, addr, config)
}
//
// Authentication helpers
//
// CreatePublicKeyVerifier can be used to create the callback
// function PublicKeyVerifier sometimes required in a libdisco.Config
// for peers that are receiving a static public key at some
// point during the handshake
func CreatePublicKeyVerifier(rootPublicKey ed25519.PublicKey) func([]byte, []byte) bool {
return func(publicKey, proof []byte) bool {
// ed25519.Verify panics if len(publicKey) is not PublicKeySize. We need to avoid that
if len(publicKey) != 32 {
return false
}
return ed25519.Verify(rootPublicKey, publicKey, proof)
}
}
// CreateStaticPublicKeyProof can be used to create the proof
// StaticPublicKeyProof sometimes required in a libdisco.Config
// for peers that are sending their static public key at some
// point during the handshake
func CreateStaticPublicKeyProof(rootPrivateKey ed25519.PrivateKey, publicKey []byte) []byte {
if len(publicKey) != 32 {
panic("disco: length of public key passed is incorrect (should be 32)")
}
signature, err := rootPrivateKey.Sign(rand.Reader, publicKey, crypto.Hash(0))
if err != nil {
panic("disco: can't create static public key proof")
}
return signature
}
//
// Storage of Disco Signing Root Keys
//
// GenerateAndSaveDiscoRootKeyPair generates an ed25519 root key pair and save the private and public parts in different files.
func GenerateAndSaveDiscoRootKeyPair(discoRootPrivateKeyFile string, discoRootPublicKeyFile string) (err error) {
// TODO: should I require a passphrase and encrypt it with it?
publicKey, privateKey, _ := ed25519.GenerateKey(rand.Reader)
var publicKeyHex [32 * 2]byte
var privateKeyHex [64 * 2]byte
hex.Encode(publicKeyHex[:], publicKey)
hex.Encode(privateKeyHex[:], privateKey)
err = ioutil.WriteFile(discoRootPrivateKeyFile, privateKeyHex[:], 0400)
if err != nil {
return err
}
err = ioutil.WriteFile(discoRootPublicKeyFile, publicKeyHex[:], 0644)
return err
}
// LoadDiscoRootPublicKey reads and parses a public Root key from a
// file. The file contains an 32-byte ed25519 public key in hexadecimal
func LoadDiscoRootPublicKey(discoRootPublicKey string) (rootPublicKey ed25519.PublicKey, err error) {
publicKeyHex, err := ioutil.ReadFile(discoRootPublicKey)
if err != nil {
return nil, err
}
if len(publicKeyHex) != 32*2 {
return nil, errors.New("Disco: Disco root public key file is not correctly formated")
}
publicKey := make([]byte, 32)
_, err = hex.Decode(publicKey[:], publicKeyHex)
if err != nil {
return nil, err
}
return publicKey, nil
}
// LoadDiscoRootPrivateKey reads and parses a private Root key from a
// file. The file contains an 32-byte ed25519 private key in hexadecimal
func LoadDiscoRootPrivateKey(discoRootPrivateKey string) (rootPrivateKey ed25519.PrivateKey, err error) {
// TODO: should I require a passphrase to decrypt it?
privateKeyHex, err := ioutil.ReadFile(discoRootPrivateKey)
if err != nil {
return nil, err
}
if len(privateKeyHex) != 64*2 {
return nil, errors.New("Disco: Disco root private key file is not correctly formated")
}
privateKey := make([]byte, 64)
_, err = hex.Decode(privateKey[:], privateKeyHex)
if err != nil {
return nil, err
}
return privateKey, nil
}
//
// Storage of Disco Static Keys
//
// GenerateAndSaveDiscoKeyPair generates a disco key pair (X25519 key pair)
// and saves it to a file in hexadecimal form. If a non-empty passphrase is passed, the file
// will be encrypted. You can use ExportPublicKey() to export the public key part.
func GenerateAndSaveDiscoKeyPair(discoKeyPairFile string, passphrase string) (keyPair *KeyPair, err error) {
keyPair = GenerateKeypair(nil)
var dataToWrite [128]byte
hex.Encode(dataToWrite[:64], keyPair.PrivateKey[:])
hex.Encode(dataToWrite[64:], keyPair.PublicKey[:])
if passphrase != "" {
key := argon2.Key([]byte(passphrase), []byte("DiscoKeyPair"), 3, 32*1024, 4, 32)
ciphertext := Encrypt(key, dataToWrite[:])
err = ioutil.WriteFile(discoKeyPairFile, ciphertext, 0400)
} else {
err = ioutil.WriteFile(discoKeyPairFile, dataToWrite[:], 0400)
}
if err != nil {
return nil, errors.New("Disco: could not write on file at path")
}
return keyPair, nil
}
// LoadDiscoKeyPair reads and parses a public/private key pair from a pair
// of files. You can pass a non-empty passphrase if the keys are stored encrypted.
func LoadDiscoKeyPair(discoKeyPairFile, passphrase string) (*KeyPair, error) {
keyPairString, err := ioutil.ReadFile(discoKeyPairFile)
if err != nil {
return nil, err
}
if passphrase != "" {
key := argon2.Key([]byte(passphrase), []byte("DiscoKeyPair"), 3, 32*1024, 4, 32)
keyPairString, err = Decrypt(key, keyPairString)
if err != nil {
return nil, err
}
}
if len(keyPairString) != 64*2 {
return nil, errors.New("Disco: Disco key pair file is not correctly formated")
}
var keyPair KeyPair
_, err = hex.Decode(keyPair.PrivateKey[:], keyPairString[:64])
if err != nil {
return nil, err
}
_, err = hex.Decode(keyPair.PublicKey[:], keyPairString[64:])
if err != nil {
return nil, err
}
return &keyPair, nil
}