Merge pull request #979 from milosgajdos83/tunnel-encrypt
[WIP] Tunnel encryption
This commit is contained in:
commit
c420fa2dec
72
tunnel/crypto.go
Normal file
72
tunnel/crypto.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hash hahes the data into 32 bytes key and returns it
|
||||||
|
// hash uses sha256 underneath to hash the supplied key
|
||||||
|
func hash(key string) []byte {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write([]byte(key))
|
||||||
|
return hasher.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts data and returns the encrypted data
|
||||||
|
func Encrypt(data []byte, key string) ([]byte, error) {
|
||||||
|
// generate a new AES cipher using our 32 byte key
|
||||||
|
c, err := aes.NewCipher(hash(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcm or Galois/Counter Mode, is a mode of operation
|
||||||
|
// for symmetric key cryptographic block ciphers
|
||||||
|
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode
|
||||||
|
gcm, err := cipher.NewGCM(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new byte array the size of the nonce
|
||||||
|
// NOTE: we might use smaller nonce size in the future
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: we prepend the nonce to the payload
|
||||||
|
// we need to do this as we need the same nonce
|
||||||
|
// to decrypt the payload when receiving it
|
||||||
|
return gcm.Seal(nonce, nonce, data, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt decrypts the payload and returns the decrypted data
|
||||||
|
func Decrypt(data []byte, key string) ([]byte, error) {
|
||||||
|
// generate AES cipher for decrypting the message
|
||||||
|
c, err := aes.NewCipher(hash(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use GCM to encrypt the payload
|
||||||
|
gcm, err := cipher.NewGCM(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := gcm.NonceSize()
|
||||||
|
// NOTE: we need to parse out nonce from the payload
|
||||||
|
// we prepend the nonce to every encrypted payload
|
||||||
|
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
41
tunnel/crypto_test.go
Normal file
41
tunnel/crypto_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncrypt(t *testing.T) {
|
||||||
|
key := "tokenpassphrase"
|
||||||
|
data := []byte("supersecret")
|
||||||
|
|
||||||
|
cipherText, err := Encrypt(data, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to encrypt data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the cipherText is not the same as data
|
||||||
|
if bytes.Equal(data, cipherText) {
|
||||||
|
t.Error("encrypted data are the same as plaintext")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecrypt(t *testing.T) {
|
||||||
|
key := "tokenpassphrase"
|
||||||
|
data := []byte("supersecret")
|
||||||
|
|
||||||
|
cipherText, err := Encrypt(data, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to encrypt data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText, err := Decrypt(cipherText, key)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to decrypt data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify the plainText is the same as data
|
||||||
|
if !bytes.Equal(data, plainText) {
|
||||||
|
t.Error("decrypted data not the same as plaintext")
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ type tun struct {
|
|||||||
// the unique id for this tunnel
|
// the unique id for this tunnel
|
||||||
id string
|
id string
|
||||||
|
|
||||||
// tunnel token for authentication
|
// tunnel token for session encryption
|
||||||
token string
|
token string
|
||||||
|
|
||||||
// to indicate if we're connected or not
|
// to indicate if we're connected or not
|
||||||
@ -119,6 +119,7 @@ func (t *tun) newSession(channel, sessionId string) (*session, bool) {
|
|||||||
tunnel: t.id,
|
tunnel: t.id,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
session: sessionId,
|
session: sessionId,
|
||||||
|
token: t.token,
|
||||||
closed: make(chan bool),
|
closed: make(chan bool),
|
||||||
recv: make(chan *message, 128),
|
recv: make(chan *message, 128),
|
||||||
send: t.send,
|
send: t.send,
|
||||||
@ -159,7 +160,6 @@ func (t *tun) announce(channel, session string, link *link) {
|
|||||||
"Micro-Tunnel-Channel": channel,
|
"Micro-Tunnel-Channel": channel,
|
||||||
"Micro-Tunnel-Session": session,
|
"Micro-Tunnel-Session": session,
|
||||||
"Micro-Tunnel-Link": link.id,
|
"Micro-Tunnel-Link": link.id,
|
||||||
"Micro-Tunnel-Token": t.token,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,9 +292,6 @@ func (t *tun) process() {
|
|||||||
// set the session id
|
// set the session id
|
||||||
newMsg.Header["Micro-Tunnel-Session"] = msg.session
|
newMsg.Header["Micro-Tunnel-Session"] = msg.session
|
||||||
|
|
||||||
// set the tunnel token
|
|
||||||
newMsg.Header["Micro-Tunnel-Token"] = t.token
|
|
||||||
|
|
||||||
// send the message via the interface
|
// send the message via the interface
|
||||||
t.RLock()
|
t.RLock()
|
||||||
|
|
||||||
@ -447,14 +444,11 @@ func (t *tun) listen(link *link) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// always ensure we have the correct auth token
|
// TODO: figure out network authentication
|
||||||
// TODO: segment the tunnel based on token
|
// for now we use tunnel token to encrypt/decrypt
|
||||||
// e.g use it as the basis
|
// session communication, but we will probably need
|
||||||
token := msg.Header["Micro-Tunnel-Token"]
|
// some sort of network authentication (token) to avoid
|
||||||
if token != t.token {
|
// having rogue actors spamming the network
|
||||||
log.Debugf("Tunnel link %s received invalid token %s", token)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// message type
|
// message type
|
||||||
mtype := msg.Header["Micro-Tunnel"]
|
mtype := msg.Header["Micro-Tunnel"]
|
||||||
@ -704,7 +698,6 @@ func (t *tun) discover(link *link) {
|
|||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
"Micro-Tunnel": "discover",
|
"Micro-Tunnel": "discover",
|
||||||
"Micro-Tunnel-Id": t.id,
|
"Micro-Tunnel-Id": t.id,
|
||||||
"Micro-Tunnel-Token": t.token,
|
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Debugf("Tunnel failed to send discover to link %s: %v", link.id, err)
|
log.Debugf("Tunnel failed to send discover to link %s: %v", link.id, err)
|
||||||
@ -735,7 +728,6 @@ func (t *tun) keepalive(link *link) {
|
|||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
"Micro-Tunnel": "keepalive",
|
"Micro-Tunnel": "keepalive",
|
||||||
"Micro-Tunnel-Id": t.id,
|
"Micro-Tunnel-Id": t.id,
|
||||||
"Micro-Tunnel-Token": t.token,
|
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err)
|
log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err)
|
||||||
@ -767,7 +759,6 @@ func (t *tun) setupLink(node string) (*link, error) {
|
|||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
"Micro-Tunnel": "connect",
|
"Micro-Tunnel": "connect",
|
||||||
"Micro-Tunnel-Id": t.id,
|
"Micro-Tunnel-Id": t.id,
|
||||||
"Micro-Tunnel-Token": t.token,
|
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -903,7 +894,6 @@ func (t *tun) close() error {
|
|||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
"Micro-Tunnel": "close",
|
"Micro-Tunnel": "close",
|
||||||
"Micro-Tunnel-Id": t.id,
|
"Micro-Tunnel-Id": t.id,
|
||||||
"Micro-Tunnel-Token": t.token,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
link.Close()
|
link.Close()
|
||||||
@ -1157,6 +1147,8 @@ func (t *tun) Listen(channel string, opts ...ListenOption) (Listener, error) {
|
|||||||
|
|
||||||
tl := &tunListener{
|
tl := &tunListener{
|
||||||
channel: channel,
|
channel: channel,
|
||||||
|
// tunnel token
|
||||||
|
token: t.token,
|
||||||
// the accept channel
|
// the accept channel
|
||||||
accept: make(chan *session, 128),
|
accept: make(chan *session, 128),
|
||||||
// the channel to close
|
// the channel to close
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
type tunListener struct {
|
type tunListener struct {
|
||||||
// address of the listener
|
// address of the listener
|
||||||
channel string
|
channel string
|
||||||
|
// token is the tunnel token
|
||||||
|
token string
|
||||||
// the accept channel
|
// the accept channel
|
||||||
accept chan *session
|
accept chan *session
|
||||||
// the channel to close
|
// the channel to close
|
||||||
@ -78,6 +80,8 @@ func (t *tunListener) process() {
|
|||||||
channel: m.channel,
|
channel: m.channel,
|
||||||
// the session id
|
// the session id
|
||||||
session: m.session,
|
session: m.session,
|
||||||
|
// tunnel token
|
||||||
|
token: t.token,
|
||||||
// is loopback conn
|
// is loopback conn
|
||||||
loopback: m.loopback,
|
loopback: m.loopback,
|
||||||
// the link the message was received on
|
// the link the message was received on
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package tunnel
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
@ -17,6 +18,8 @@ type session struct {
|
|||||||
channel string
|
channel string
|
||||||
// the session id based on Micro.Tunnel-Session
|
// the session id based on Micro.Tunnel-Session
|
||||||
session string
|
session string
|
||||||
|
// token is the session token
|
||||||
|
token string
|
||||||
// closed
|
// closed
|
||||||
closed chan bool
|
closed chan bool
|
||||||
// remote addr
|
// remote addr
|
||||||
@ -301,14 +304,29 @@ func (s *session) Send(m *transport.Message) error {
|
|||||||
// no op
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encrypt the transport message payload
|
||||||
|
body, err := Encrypt(m.Body, s.token+s.channel+s.session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to encrypt message body: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// make copy
|
// make copy
|
||||||
data := &transport.Message{
|
data := &transport.Message{
|
||||||
Header: make(map[string]string),
|
Header: make(map[string]string),
|
||||||
Body: m.Body,
|
Body: body,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// encrypt all the headers
|
||||||
for k, v := range m.Header {
|
for k, v := range m.Header {
|
||||||
data.Header[k] = v
|
// encrypt the transport message payload
|
||||||
|
val, err := Encrypt([]byte(v), s.token+s.channel+s.session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to encrypt message header %s: %v", k, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// hex encode the encrypted header value
|
||||||
|
data.Header[k] = hex.EncodeToString(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new message
|
// create a new message
|
||||||
@ -352,7 +370,35 @@ func (s *session) Recv(m *transport.Message) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("Received %+v from recv backlog", msg)
|
//log.Tracef("Received %+v from recv backlog", msg)
|
||||||
|
log.Debugf("Received %+v from recv backlog", msg)
|
||||||
|
|
||||||
|
// decrypt the received payload using the token
|
||||||
|
body, err := Decrypt(msg.data.Body, s.token+s.channel+s.session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to decrypt message body: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.data.Body = body
|
||||||
|
|
||||||
|
// encrypt all the headers
|
||||||
|
for k, v := range msg.data.Header {
|
||||||
|
// hex decode the header values
|
||||||
|
h, err := hex.DecodeString(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to decode message header %s: %v", k, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// encrypt the transport message payload
|
||||||
|
val, err := Decrypt([]byte(h), s.token+s.channel+s.session)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to decrypt message header %s: %v", k, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// hex encode the encrypted header value
|
||||||
|
msg.data.Header[k] = string(val)
|
||||||
|
}
|
||||||
|
|
||||||
// set message
|
// set message
|
||||||
*m = *msg.data
|
*m = *msg.data
|
||||||
// return nil
|
// return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user