From 61fe552ac492ccbff62391ca0842d641475630be Mon Sep 17 00:00:00 2001
From: Milos Gajdos <milosgajdos83@gmail.com>
Date: Mon, 25 Nov 2019 14:58:12 +0000
Subject: [PATCH 1/3] First commit: Outline of tunnel encryption code

---
 tunnel/crypto.go      | 72 +++++++++++++++++++++++++++++++++++++++++++
 tunnel/crypto_test.go | 41 ++++++++++++++++++++++++
 2 files changed, 113 insertions(+)
 create mode 100644 tunnel/crypto.go
 create mode 100644 tunnel/crypto_test.go

diff --git a/tunnel/crypto.go b/tunnel/crypto.go
new file mode 100644
index 00000000..89a0bdbf
--- /dev/null
+++ b/tunnel/crypto.go
@@ -0,0 +1,72 @@
+package tunnel
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
+	"crypto/sha256"
+	"io"
+)
+
+// Encrypt encrypts data and returns encrypted payload
+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 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
+}
+
+// hash hahes the data into 32 bytes key and returns it
+// hash uses sha256 to hash the passed in string.
+func hash(key string) []byte {
+	hasher := sha256.New()
+	hasher.Write([]byte(key))
+	return hasher.Sum(nil)
+}
diff --git a/tunnel/crypto_test.go b/tunnel/crypto_test.go
new file mode 100644
index 00000000..6f8498b9
--- /dev/null
+++ b/tunnel/crypto_test.go
@@ -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")
+	}
+}

From f82c267d8141013f2af6910bc960278540d883bd Mon Sep 17 00:00:00 2001
From: Milos Gajdos <milosgajdos83@gmail.com>
Date: Mon, 25 Nov 2019 15:34:41 +0000
Subject: [PATCH 2/3] Encrypt session communication

---
 tunnel/crypto.go  |  6 +++---
 tunnel/session.go | 31 ++++++++++++++++++++++++++++++-
 2 files changed, 33 insertions(+), 4 deletions(-)

diff --git a/tunnel/crypto.go b/tunnel/crypto.go
index 89a0bdbf..f34f9c15 100644
--- a/tunnel/crypto.go
+++ b/tunnel/crypto.go
@@ -8,7 +8,7 @@ import (
 	"io"
 )
 
-// Encrypt encrypts data and returns encrypted payload
+// 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))
@@ -37,7 +37,7 @@ func Encrypt(data []byte, key string) ([]byte, error) {
 	return gcm.Seal(nonce, nonce, data, nil), nil
 }
 
-// Decrypt decrypts the payload and returns decrypted data
+// 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))
@@ -64,7 +64,7 @@ func Decrypt(data []byte, key string) ([]byte, error) {
 }
 
 // hash hahes the data into 32 bytes key and returns it
-// hash uses sha256 to hash the passed in string.
+// hash uses sha256 underneath to hash the supplied key
 func hash(key string) []byte {
 	hasher := sha256.New()
 	hasher.Write([]byte(key))
diff --git a/tunnel/session.go b/tunnel/session.go
index 59cbdb5e..09042fd1 100644
--- a/tunnel/session.go
+++ b/tunnel/session.go
@@ -301,13 +301,27 @@ func (s *session) Send(m *transport.Message) error {
 		// no op
 	}
 
+	// get the token
+	token, ok := m.Header["Micro-Tunnel-Token"]
+	if !ok {
+		// TODO: should we continue or return error
+		log.Debugf("no token found, insecure channel")
+	}
+
+	// encrypt the transport message payload
+	body, err := Encrypt(m.Body, token+s.channel+s.session)
+	if err != nil {
+		return err
+	}
+
 	// make copy
 	data := &transport.Message{
 		Header: make(map[string]string),
-		Body:   m.Body,
+		Body:   body,
 	}
 
 	for k, v := range m.Header {
+		// TODO: should we also encrypt headers?
 		data.Header[k] = v
 	}
 
@@ -352,7 +366,22 @@ func (s *session) Recv(m *transport.Message) error {
 	default:
 	}
 
+	// TODO: if we encrypt headers we will have to decrypt them here
+	token, ok := msg.data.Header["Micro-Tunnel-Token"]
+	if !ok {
+		// TODO: should we continue or return error
+		log.Debugf("no token found, insecure channel")
+	}
+
 	log.Tracef("Received %+v from recv backlog", msg)
+
+	// decrypt the received payload using the token
+	body, err := Decrypt(msg.data.Body, token+s.channel+s.session)
+	if err != nil {
+		return err
+	}
+	msg.data.Body = body
+
 	// set message
 	*m = *msg.data
 	// return nil

From 9095b99f6bc8dcbff48a02002812a1f3961bfe26 Mon Sep 17 00:00:00 2001
From: Milos Gajdos <milosgajdos83@gmail.com>
Date: Mon, 25 Nov 2019 18:56:00 +0000
Subject: [PATCH 3/3] Token has been stripped; Headers are encrypted

---
 tunnel/crypto.go   | 16 +++++++-------
 tunnel/default.go  | 42 ++++++++++++++---------------------
 tunnel/listener.go |  4 ++++
 tunnel/session.go  | 55 ++++++++++++++++++++++++++++++----------------
 4 files changed, 65 insertions(+), 52 deletions(-)

diff --git a/tunnel/crypto.go b/tunnel/crypto.go
index f34f9c15..ba0d5057 100644
--- a/tunnel/crypto.go
+++ b/tunnel/crypto.go
@@ -8,6 +8,14 @@ import (
 	"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
@@ -62,11 +70,3 @@ func Decrypt(data []byte, key string) ([]byte, error) {
 
 	return plaintext, nil
 }
-
-// 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)
-}
diff --git a/tunnel/default.go b/tunnel/default.go
index adf4bb6d..72963f0c 100644
--- a/tunnel/default.go
+++ b/tunnel/default.go
@@ -30,7 +30,7 @@ type tun struct {
 	// the unique id for this tunnel
 	id string
 
-	// tunnel token for authentication
+	// tunnel token for session encryption
 	token string
 
 	// to indicate if we're connected or not
@@ -119,6 +119,7 @@ func (t *tun) newSession(channel, sessionId string) (*session, bool) {
 		tunnel:  t.id,
 		channel: channel,
 		session: sessionId,
+		token:   t.token,
 		closed:  make(chan bool),
 		recv:    make(chan *message, 128),
 		send:    t.send,
@@ -159,7 +160,6 @@ func (t *tun) announce(channel, session string, link *link) {
 			"Micro-Tunnel-Channel": channel,
 			"Micro-Tunnel-Session": session,
 			"Micro-Tunnel-Link":    link.id,
-			"Micro-Tunnel-Token":   t.token,
 		},
 	}
 
@@ -292,9 +292,6 @@ func (t *tun) process() {
 			// set the session id
 			newMsg.Header["Micro-Tunnel-Session"] = msg.session
 
-			// set the tunnel token
-			newMsg.Header["Micro-Tunnel-Token"] = t.token
-
 			// send the message via the interface
 			t.RLock()
 
@@ -447,14 +444,11 @@ func (t *tun) listen(link *link) {
 			return
 		}
 
-		// always ensure we have the correct auth token
-		// TODO: segment the tunnel based on token
-		// e.g use it as the basis
-		token := msg.Header["Micro-Tunnel-Token"]
-		if token != t.token {
-			log.Debugf("Tunnel link %s received invalid token %s", token)
-			return
-		}
+		// TODO: figure out network authentication
+		// for now we use tunnel token to encrypt/decrypt
+		// session communication, but we will probably need
+		// some sort of network authentication (token) to avoid
+		// having rogue actors spamming the network
 
 		// message type
 		mtype := msg.Header["Micro-Tunnel"]
@@ -702,9 +696,8 @@ func (t *tun) discover(link *link) {
 			// send a discovery message to all links
 			if err := link.Send(&transport.Message{
 				Header: map[string]string{
-					"Micro-Tunnel":       "discover",
-					"Micro-Tunnel-Id":    t.id,
-					"Micro-Tunnel-Token": t.token,
+					"Micro-Tunnel":    "discover",
+					"Micro-Tunnel-Id": t.id,
 				},
 			}); err != nil {
 				log.Debugf("Tunnel failed to send discover to link %s: %v", link.id, err)
@@ -733,9 +726,8 @@ func (t *tun) keepalive(link *link) {
 			log.Debugf("Tunnel sending keepalive to link: %v", link.Remote())
 			if err := link.Send(&transport.Message{
 				Header: map[string]string{
-					"Micro-Tunnel":       "keepalive",
-					"Micro-Tunnel-Id":    t.id,
-					"Micro-Tunnel-Token": t.token,
+					"Micro-Tunnel":    "keepalive",
+					"Micro-Tunnel-Id": t.id,
 				},
 			}); err != nil {
 				log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err)
@@ -765,9 +757,8 @@ func (t *tun) setupLink(node string) (*link, error) {
 	// send the first connect message
 	if err := link.Send(&transport.Message{
 		Header: map[string]string{
-			"Micro-Tunnel":       "connect",
-			"Micro-Tunnel-Id":    t.id,
-			"Micro-Tunnel-Token": t.token,
+			"Micro-Tunnel":    "connect",
+			"Micro-Tunnel-Id": t.id,
 		},
 	}); err != nil {
 		return nil, err
@@ -901,9 +892,8 @@ func (t *tun) close() error {
 	for node, link := range t.links {
 		link.Send(&transport.Message{
 			Header: map[string]string{
-				"Micro-Tunnel":       "close",
-				"Micro-Tunnel-Id":    t.id,
-				"Micro-Tunnel-Token": t.token,
+				"Micro-Tunnel":    "close",
+				"Micro-Tunnel-Id": t.id,
 			},
 		})
 		link.Close()
@@ -1157,6 +1147,8 @@ func (t *tun) Listen(channel string, opts ...ListenOption) (Listener, error) {
 
 	tl := &tunListener{
 		channel: channel,
+		// tunnel token
+		token: t.token,
 		// the accept channel
 		accept: make(chan *session, 128),
 		// the channel to close
diff --git a/tunnel/listener.go b/tunnel/listener.go
index c893297d..cc207d70 100644
--- a/tunnel/listener.go
+++ b/tunnel/listener.go
@@ -10,6 +10,8 @@ import (
 type tunListener struct {
 	// address of the listener
 	channel string
+	// token is the tunnel token
+	token string
 	// the accept channel
 	accept chan *session
 	// the channel to close
@@ -78,6 +80,8 @@ func (t *tunListener) process() {
 					channel: m.channel,
 					// the session id
 					session: m.session,
+					// tunnel token
+					token: t.token,
 					// is loopback conn
 					loopback: m.loopback,
 					// the link the message was received on
diff --git a/tunnel/session.go b/tunnel/session.go
index 09042fd1..4b8721aa 100644
--- a/tunnel/session.go
+++ b/tunnel/session.go
@@ -1,6 +1,7 @@
 package tunnel
 
 import (
+	"encoding/hex"
 	"errors"
 	"io"
 	"time"
@@ -17,6 +18,8 @@ type session struct {
 	channel string
 	// the session id based on Micro.Tunnel-Session
 	session string
+	// token is the session token
+	token string
 	// closed
 	closed chan bool
 	// remote addr
@@ -301,16 +304,10 @@ func (s *session) Send(m *transport.Message) error {
 		// no op
 	}
 
-	// get the token
-	token, ok := m.Header["Micro-Tunnel-Token"]
-	if !ok {
-		// TODO: should we continue or return error
-		log.Debugf("no token found, insecure channel")
-	}
-
 	// encrypt the transport message payload
-	body, err := Encrypt(m.Body, token+s.channel+s.session)
+	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
 	}
 
@@ -320,9 +317,16 @@ func (s *session) Send(m *transport.Message) error {
 		Body:   body,
 	}
 
+	// encrypt all the headers
 	for k, v := range m.Header {
-		// TODO: should we also encrypt headers?
-		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
@@ -366,22 +370,35 @@ func (s *session) Recv(m *transport.Message) error {
 	default:
 	}
 
-	// TODO: if we encrypt headers we will have to decrypt them here
-	token, ok := msg.data.Header["Micro-Tunnel-Token"]
-	if !ok {
-		// TODO: should we continue or return error
-		log.Debugf("no token found, insecure channel")
-	}
-
-	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, token+s.channel+s.session)
+	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
 	*m = *msg.data
 	// return nil