From 61fe552ac492ccbff62391ca0842d641475630be Mon Sep 17 00:00:00 2001 From: Milos Gajdos 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 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 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