Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Repeating Nonces #292

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 78 additions & 13 deletions aead.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
package noise

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io"
"math/rand"
"math"
)

type aeadEncryption struct {
suite cipher.AEAD
// GCM takes a 12 byte nonce by default
fixed [4]byte //Random part, 4 bytes
counter uint64 //Counter 8 bytes
}

func newAEAD(key []byte) (aeadEncryption, error) {
core, err := aes.NewCipher(key)
if err != nil {
return aeadEncryption{}, err
}

suite, err := cipher.NewGCM(core)
if err != nil {
return aeadEncryption{}, err
}

var encryption aeadEncryption
encryption.suite = suite

return encryption, encryption.regenerateNonce()
}

func extendFront(buf []byte, n int) []byte {
if len(buf) < n {
clone := make([]byte, n+len(buf))
Expand All @@ -25,26 +53,63 @@ func extendBack(buf []byte, n int) []byte {
return buf[:n]
}

func encryptAEAD(suite cipher.AEAD, buf []byte) ([]byte, error) {
a, b := suite.NonceSize(), len(buf)
func (e *aeadEncryption) regenerateNonce() error {
e.counter = 0

//Generate fixed portion of repetition resistant nonce
//https://tools.ietf.org/id/draft-mcgrew-iv-gen-01.html
if _, err := rand.Read(e.fixed[:]); err != nil {
return err
}
return nil
}

func (e *aeadEncryption) initialised() bool {
return e.suite == nil
}

func (e *aeadEncryption) encrypt(buf []byte) ([]byte, error) {
nonceSize, plaintextSize := e.suite.NonceSize(), len(buf)

buf = extendFront(buf, nonceSize)
buf = extendBack(buf, plaintextSize)

//Repetition resistant nonce https://tools.ietf.org/html/rfc5116#section-3.2

buf = extendFront(buf, a)
buf = extendBack(buf, b)
copy(buf[:nonceSize], e.fixed[:])

if _, err := rand.Read(buf[:a]); err != nil {
return nil, err
binary.BigEndian.PutUint64(buf[len(e.fixed):nonceSize], e.counter)

//Increment Nonce counter
e.counter++

if math.MaxUint64 == e.counter { // Stop nonce reuse after 2^64 messages
e.regenerateNonce()
}

return append(buf[:a], suite.Seal(buf[a:a], buf[:a], buf[a:a+b], nil)...), nil
//Reuse the storage of buf, taking nonce buf[:nonceSize] and plaintext[nonceSize:nonceSize+plaintextSize]
//Put nonce on the front of the ciphertext
return append(buf[:nonceSize], e.suite.Seal(buf[nonceSize:nonceSize], buf[:nonceSize], buf[nonceSize:nonceSize+plaintextSize], nil)...), nil
}

func decryptAEAD(suite cipher.AEAD, buf []byte) ([]byte, error) {
if len(buf) < suite.NonceSize() {
func (e *aeadEncryption) decrypt(buf []byte) ([]byte, error) {
if len(buf) < e.suite.NonceSize() {
return nil, io.ErrUnexpectedEOF
}

nonce := buf[:suite.NonceSize()]
text := buf[suite.NonceSize():]
nonce := buf[:e.suite.NonceSize()]
text := buf[e.suite.NonceSize():]

return suite.Open(text[:0], nonce, text, nil)
cleartext, err := e.suite.Open(text[:0], nonce, text, nil)
if err != nil {
return cleartext, err
}

// Handle edge case where both parties generate the same 4 starting bytes
// The best solution to this would be the parties generate a nonce prefix together
// This also has some chance of still generating the same data
if bytes.Equal(e.fixed[:], nonce[:4]) {
e.regenerateNonce()
}
return cleartext, err
}
31 changes: 10 additions & 21 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ package noise
import (
"bufio"
"context"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"net"
"sync"
"time"

"go.uber.org/zap"
)

type clientSide bool
Expand Down Expand Up @@ -52,7 +51,7 @@ type Client struct {
addr string
side clientSide

suite cipher.AEAD
suite aeadEncryption

logger struct {
sync.RWMutex
Expand Down Expand Up @@ -300,11 +299,11 @@ func (c *Client) read() ([]byte, error) {
return nil, err
}

if c.suite == nil {
if c.suite.initialised() {
return c.readerBuf[4 : size+4], nil
}

buf, err := decryptAEAD(c.suite, c.readerBuf[4:size+4])
buf, err := c.suite.decrypt(c.readerBuf[4 : size+4])
if err != nil {
return nil, err
}
Expand All @@ -319,10 +318,10 @@ func (c *Client) write(data []byte) error {
}
}

if c.suite != nil {
if c.suite.initialised() {
var err error

if data, err = encryptAEAD(c.suite, data); err != nil {
if data, err = c.suite.encrypt(data); err != nil {
return err
}
}
Expand Down Expand Up @@ -436,20 +435,10 @@ func (c *Client) handshake() {
// Use the derived shared key from Diffie-Hellman to encrypt/decrypt all future communications
// with AES-256 Galois Counter Mode (GCM).

core, err := aes.NewCipher(shared[:])
c.suite, err = newAEAD(shared[:])
if err != nil {
c.reportError(fmt.Errorf("could not instantiate aes: %w", err))
return
}

suite, err := cipher.NewGCM(core)
if err != nil {
c.reportError(fmt.Errorf("could not instantiate aes-gcm: %w", err))
return
}

c.suite = suite

// Send to our peer our overlay ID.

buf := c.node.id.Marshal()
Expand Down Expand Up @@ -600,10 +589,10 @@ Write:
buf = buf[:0]
buf = msg.marshal(buf)

if c.suite != nil {
if c.suite.initialised() {
var err error

if buf, err = encryptAEAD(c.suite, buf); err != nil {
if buf, err = c.suite.encrypt(buf); err != nil {
c.Logger().Warn("Got an error encrypting a message.", zap.Error(err))
c.reportError(err)
break Write
Expand Down