Merge pull request #979 from milosgajdos83/tunnel-encrypt

[WIP] Tunnel encryption
This commit is contained in:
Asim Aslam 2019-11-25 19:12:24 +00:00 committed by GitHub
commit c420fa2dec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 183 additions and 28 deletions

72
tunnel/crypto.go Normal file
View 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
View 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")
}
}

View File

@ -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

View File

@ -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

View File

@ -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