Implemented MAC1/2 calculation

This commit is contained in:
Mathias Hall-Andersen 2017-06-27 17:33:06 +02:00
parent eb75ff430d
commit 8236f3afa2
13 changed files with 454 additions and 85 deletions

View file

@ -81,7 +81,7 @@ func ipcSetOperation(device *Device, socket *bufio.ReadWriter) *IPCError {
} }
case "listen_port": case "listen_port":
_, err := fmt.Sscanf(value, "%ud", &device.listenPort) _, err := fmt.Sscanf(value, "%ud", &device.address.Port)
if err != nil { if err != nil {
return &IPCError{Code: ipcErrorInvalidPort} return &IPCError{Code: ipcErrorInvalidPort}
} }

16
src/constants.go Normal file
View file

@ -0,0 +1,16 @@
package main
import (
"time"
)
const (
RekeyAfterMessage = (1 << 64) - (1 << 16) - 1
RekeyAfterTime = time.Second * 120
RekeyAttemptTime = time.Second * 90
RekeyTimeout = time.Second * 5
RejectAfterTime = time.Second * 180
RejectAfterMessage = (1 << 64) - (1 << 4) - 1
KeepaliveTimeout = time.Second * 10
CookieRefreshTime = time.Second * 2
)

View file

@ -1,39 +0,0 @@
package main
import (
"errors"
"golang.org/x/crypto/blake2s"
)
func CalculateCookie(peer *Peer, msg []byte) {
size := len(msg)
if size < blake2s.Size128*2 {
panic(errors.New("bug: message too short"))
}
startMac1 := size - (blake2s.Size128 * 2)
startMac2 := size - blake2s.Size128
mac1 := msg[startMac1 : startMac1+blake2s.Size128]
mac2 := msg[startMac2 : startMac2+blake2s.Size128]
peer.mutex.RLock()
defer peer.mutex.RUnlock()
// set mac1
func() {
mac, _ := blake2s.New128(peer.macKey[:])
mac.Write(msg[:startMac1])
mac.Sum(mac1[:0])
}()
// set mac2
if peer.cookie != nil {
mac, _ := blake2s.New128(peer.cookie)
mac.Write(msg[:startMac2])
mac.Sum(mac2[:0])
}
}

View file

@ -1,25 +1,24 @@
package main package main
import ( import (
"log"
"net" "net"
"sync" "sync"
) )
type Device struct { type Device struct {
mtu int mtu int
source *net.UDPAddr // UDP source address fwMark uint32
address *net.UDPAddr // UDP source address
conn *net.UDPConn // UDP "connection" conn *net.UDPConn // UDP "connection"
mutex sync.RWMutex mutex sync.RWMutex
peers map[NoisePublicKey]*Peer
indices IndexTable
privateKey NoisePrivateKey privateKey NoisePrivateKey
publicKey NoisePublicKey publicKey NoisePublicKey
fwMark uint32
listenPort uint16
routingTable RoutingTable routingTable RoutingTable
logger log.Logger indices IndexTable
log *Logger
queueWorkOutbound chan *OutboundWorkQueueElement queueWorkOutbound chan *OutboundWorkQueueElement
peers map[NoisePublicKey]*Peer
mac MacStateDevice
} }
func (device *Device) SetPrivateKey(sk NoisePrivateKey) { func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
@ -30,8 +29,9 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
device.privateKey = sk device.privateKey = sk
device.publicKey = sk.publicKey() device.publicKey = sk.publicKey()
device.mac.Init(device.publicKey)
// do precomputations // do DH precomputations
for _, peer := range device.peers { for _, peer := range device.peers {
h := &peer.handshake h := &peer.handshake
@ -45,9 +45,9 @@ func (device *Device) Init() {
device.mutex.Lock() device.mutex.Lock()
defer device.mutex.Unlock() defer device.mutex.Unlock()
device.log = NewLogger()
device.peers = make(map[NoisePublicKey]*Peer) device.peers = make(map[NoisePublicKey]*Peer)
device.indices.Init() device.indices.Init()
device.listenPort = 0
device.routingTable.Reset() device.routingTable.Reset()
} }

View file

@ -3,6 +3,7 @@ package main
import ( import (
"crypto/cipher" "crypto/cipher"
"sync" "sync"
"time"
) )
type KeyPair struct { type KeyPair struct {
@ -10,6 +11,8 @@ type KeyPair struct {
recvNonce uint64 recvNonce uint64
send cipher.AEAD send cipher.AEAD
sendNonce uint64 sendNonce uint64
isInitiator bool
created time.Time
} }
type KeyPairs struct { type KeyPairs struct {

35
src/logger.go Normal file
View file

@ -0,0 +1,35 @@
package main
import (
"log"
"os"
)
const (
LogLevelError = iota
LogLevelInfo
LogLevelDebug
)
type Logger struct {
Debug *log.Logger
Info *log.Logger
Error *log.Logger
}
func NewLogger() *Logger {
logger := new(Logger)
logger.Debug = log.New(os.Stdout,
"DEBUG: ",
log.Ldate|log.Ltime|log.Lshortfile,
)
logger.Info = log.New(os.Stdout,
"INFO: ",
log.Ldate|log.Ltime|log.Lshortfile,
)
logger.Error = log.New(os.Stdout,
"ERROR: ",
log.Ldate|log.Ltime|log.Lshortfile,
)
return logger
}

161
src/macs_device.go Normal file
View file

@ -0,0 +1,161 @@
package main
import (
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"github.com/aead/chacha20poly1305" // Needed for XChaCha20Poly1305, TODO:
"golang.org/x/crypto/blake2s"
"net"
"sync"
"time"
)
type MacStateDevice struct {
mutex sync.RWMutex
refreshed time.Time
secret [blake2s.Size]byte
keyMac1 [blake2s.Size]byte
xaead cipher.AEAD
}
func (state *MacStateDevice) Init(pk NoisePublicKey) {
state.mutex.Lock()
defer state.mutex.Unlock()
func() {
hsh, _ := blake2s.New256(nil)
hsh.Write([]byte(WGLabelMAC1))
hsh.Write(pk[:])
hsh.Sum(state.keyMac1[:0])
}()
state.xaead, _ = chacha20poly1305.NewXCipher(state.keyMac1[:])
state.refreshed = time.Time{} // never
}
func (state *MacStateDevice) CheckMAC1(msg []byte) bool {
size := len(msg)
startMac1 := size - (blake2s.Size128 * 2)
startMac2 := size - blake2s.Size128
var mac1 [blake2s.Size128]byte
func() {
mac, _ := blake2s.New128(state.keyMac1[:])
mac.Write(msg[:startMac1])
mac.Sum(mac1[:0])
}()
return hmac.Equal(mac1[:], msg[startMac1:startMac2])
}
func (state *MacStateDevice) CheckMAC2(msg []byte, addr *net.UDPAddr) bool {
state.mutex.RLock()
defer state.mutex.RUnlock()
if time.Now().Sub(state.refreshed) > CookieRefreshTime {
return false
}
// derive cookie key
var cookie [blake2s.Size128]byte
func() {
port := [2]byte{byte(addr.Port >> 8), byte(addr.Port)}
mac, _ := blake2s.New128(state.secret[:])
mac.Write(addr.IP)
mac.Write(port[:])
mac.Sum(cookie[:0])
}()
// calculate mac of packet
start := len(msg) - blake2s.Size128
var mac2 [blake2s.Size128]byte
func() {
mac, _ := blake2s.New128(cookie[:])
mac.Write(msg[:start])
mac.Sum(mac2[:0])
}()
return hmac.Equal(mac2[:], msg[start:])
}
func (device *Device) CreateMessageCookieReply(msg []byte, receiver uint32, addr *net.UDPAddr) (*MessageCookieReply, error) {
state := &device.mac
state.mutex.RLock()
// refresh cookie secret
if time.Now().Sub(state.refreshed) > CookieRefreshTime {
state.mutex.RUnlock()
state.mutex.Lock()
_, err := rand.Read(state.secret[:])
if err != nil {
state.mutex.Unlock()
return nil, err
}
state.refreshed = time.Now()
state.mutex.Unlock()
state.mutex.RLock()
}
// derive cookie key
var cookie [blake2s.Size128]byte
func() {
port := [2]byte{byte(addr.Port >> 8), byte(addr.Port)}
mac, _ := blake2s.New128(state.secret[:])
mac.Write(addr.IP)
mac.Write(port[:])
mac.Sum(cookie[:0])
}()
// encrypt cookie
size := len(msg)
startMac1 := size - (blake2s.Size128 * 2)
startMac2 := size - blake2s.Size128
M := msg[startMac1:startMac2]
reply := new(MessageCookieReply)
reply.Type = MessageCookieReplyType
reply.Receiver = receiver
_, err := rand.Read(reply.Nonce[:])
if err != nil {
state.mutex.RUnlock()
return nil, err
}
state.xaead.Seal(reply.Cookie[:0], reply.Nonce[:], cookie[:], M)
state.mutex.RUnlock()
return reply, nil
}
func (device *Device) ConsumeMessageCookieReply(msg *MessageCookieReply) bool {
if msg.Type != MessageCookieReplyType {
return false
}
// lookup peer
lookup := device.indices.Lookup(msg.Receiver)
if lookup.handshake == nil {
return false
}
// decrypt and store cookie
var cookie [blake2s.Size128]byte
state := &lookup.peer.mac
state.mutex.Lock()
defer state.mutex.Unlock()
_, err := state.xaead.Open(cookie[:0], msg.Nonce[:], msg.Cookie[:], state.lastMac1[:])
if err != nil {
return false
}
state.cookieSet = time.Now()
state.cookie = cookie
return true
}

73
src/macs_peer.go Normal file
View file

@ -0,0 +1,73 @@
package main
import (
"crypto/cipher"
"errors"
"github.com/aead/chacha20poly1305" // Needed for XChaCha20Poly1305, TODO:
"golang.org/x/crypto/blake2s"
"sync"
"time"
)
type MacStatePeer struct {
mutex sync.RWMutex
cookieSet time.Time
cookie [blake2s.Size128]byte
lastMac1 [blake2s.Size128]byte
keyMac1 [blake2s.Size]byte
xaead cipher.AEAD
}
func (state *MacStatePeer) Init(pk NoisePublicKey) {
state.mutex.Lock()
defer state.mutex.Unlock()
func() {
hsh, _ := blake2s.New256(nil)
hsh.Write([]byte(WGLabelMAC1))
hsh.Write(pk[:])
hsh.Sum(state.keyMac1[:0])
}()
state.xaead, _ = chacha20poly1305.NewXCipher(state.keyMac1[:])
state.cookieSet = time.Time{} // never
}
func (state *MacStatePeer) AddMacs(msg []byte) {
size := len(msg)
if size < blake2s.Size128*2 {
panic(errors.New("bug: message too short"))
}
startMac1 := size - (blake2s.Size128 * 2)
startMac2 := size - blake2s.Size128
mac1 := msg[startMac1 : startMac1+blake2s.Size128]
mac2 := msg[startMac2 : startMac2+blake2s.Size128]
state.mutex.Lock()
defer state.mutex.Unlock()
// set mac1
func() {
mac, _ := blake2s.New128(state.keyMac1[:])
mac.Write(msg[:startMac1])
mac.Sum(state.lastMac1[:0])
}()
copy(mac1, state.lastMac1[:])
// set mac2
if state.cookieSet.IsZero() {
return
}
if time.Now().Sub(state.cookieSet) > CookieRefreshTime {
state.cookieSet = time.Time{}
return
}
func() {
mac, _ := blake2s.New128(state.cookie[:])
mac.Write(msg[:startMac2])
mac.Sum(mac2[:0])
}()
}

113
src/macs_test.go Normal file
View file

@ -0,0 +1,113 @@
package main
import (
"bytes"
"net"
"testing"
"testing/quick"
)
func TestMAC1(t *testing.T) {
dev1 := newDevice(t)
dev2 := newDevice(t)
peer1 := dev2.NewPeer(dev1.privateKey.publicKey())
peer2 := dev1.NewPeer(dev2.privateKey.publicKey())
assertEqual(t, peer1.mac.keyMac1[:], dev1.mac.keyMac1[:])
assertEqual(t, peer2.mac.keyMac1[:], dev2.mac.keyMac1[:])
msg1 := make([]byte, 256)
copy(msg1, []byte("some content"))
peer1.mac.AddMacs(msg1)
if dev1.mac.CheckMAC1(msg1) == false {
t.Fatal("failed to verify mac1")
}
}
func TestMACs(t *testing.T) {
assertion := func(
addr net.UDPAddr,
addrInvalid net.UDPAddr,
sk1 NoisePrivateKey,
sk2 NoisePrivateKey,
msg []byte,
receiver uint32,
) bool {
var device1 Device
device1.Init()
device1.SetPrivateKey(sk1)
var device2 Device
device2.Init()
device2.SetPrivateKey(sk2)
peer1 := device2.NewPeer(device1.privateKey.publicKey())
peer2 := device1.NewPeer(device2.privateKey.publicKey())
if addr.Port < 0 {
return true
}
addr.Port &= 0xffff
if len(msg) < 32 {
return true
}
if bytes.Compare(peer1.mac.keyMac1[:], device1.mac.keyMac1[:]) != 0 {
return false
}
if bytes.Compare(peer2.mac.keyMac1[:], device2.mac.keyMac1[:]) != 0 {
return false
}
device2.indices.Insert(receiver, IndexTableEntry{
peer: peer1,
handshake: &peer1.handshake,
})
// test just MAC1
peer1.mac.AddMacs(msg)
if device1.mac.CheckMAC1(msg) == false {
return false
}
// exchange cookie reply
cr, err := device1.CreateMessageCookieReply(msg, receiver, &addr)
if err != nil {
return false
}
if device2.ConsumeMessageCookieReply(cr) == false {
return false
}
// test MAC1 + MAC2
peer1.mac.AddMacs(msg)
if device1.mac.CheckMAC1(msg) == false {
return false
}
if device1.mac.CheckMAC2(msg, &addr) == false {
return false
}
// test invalid
if device1.mac.CheckMAC2(msg, &addrInvalid) {
return false
}
msg[5] ^= 1
if device1.mac.CheckMAC1(msg) {
return false
}
return true
}
err := quick.Check(assertion, nil)
if err != nil {
t.Error(err)
}
}

View file

@ -26,13 +26,18 @@ const (
const ( const (
MessageInitiationType = 1 MessageInitiationType = 1
MessageResponseType = 2 MessageResponseType = 2
MessageCookieResponseType = 3 MessageCookieReplyType = 3
MessageTransportType = 4 MessageTransportType = 4
) )
const (
MessageInitiationSize = 148
MessageResponseSize = 92
)
/* Type is an 8-bit field, followed by 3 nul bytes, /* Type is an 8-bit field, followed by 3 nul bytes,
* by marshalling the messages in little-endian byteorder * by marshalling the messages in little-endian byteorder
* we can treat these as a 32-bit int * we can treat these as a 32-bit unsigned int (for now)
* *
*/ */
@ -63,6 +68,13 @@ type MessageTransport struct {
Content []byte Content []byte
} }
type MessageCookieReply struct {
Type uint32
Receiver uint32
Nonce [24]byte
Cookie [blake2s.Size128 + poly1305.TagSize]byte
}
type Handshake struct { type Handshake struct {
state int state int
mutex sync.Mutex mutex sync.Mutex

View file

@ -18,6 +18,17 @@ func assertEqual(t *testing.T, a []byte, b []byte) {
} }
} }
func newDevice(t *testing.T) *Device {
var device Device
sk, err := newPrivateKey()
if err != nil {
t.Fatal(err)
}
device.Init()
device.SetPrivateKey(sk)
return &device
}
func TestCurveWrappers(t *testing.T) { func TestCurveWrappers(t *testing.T) {
sk1, err := newPrivateKey() sk1, err := newPrivateKey()
assertNil(t, err) assertNil(t, err)
@ -36,17 +47,6 @@ func TestCurveWrappers(t *testing.T) {
} }
} }
func newDevice(t *testing.T) *Device {
var device Device
sk, err := newPrivateKey()
if err != nil {
t.Fatal(err)
}
device.Init()
device.SetPrivateKey(sk)
return &device
}
func TestNoiseHandshake(t *testing.T) { func TestNoiseHandshake(t *testing.T) {
dev1 := newDevice(t) dev1 := newDevice(t)

View file

@ -2,7 +2,6 @@ package main
import ( import (
"errors" "errors"
"golang.org/x/crypto/blake2s"
"net" "net"
"sync" "sync"
"time" "time"
@ -19,12 +18,10 @@ type Peer struct {
keyPairs KeyPairs keyPairs KeyPairs
handshake Handshake handshake Handshake
device *Device device *Device
macKey [blake2s.Size]byte // Hash(Label-Mac1 || publicKey)
cookie []byte // cookie
cookieExpire time.Time
queueInbound chan []byte queueInbound chan []byte
queueOutbound chan *OutboundWorkQueueElement queueOutbound chan *OutboundWorkQueueElement
queueOutboundRouting chan []byte queueOutboundRouting chan []byte
mac MacStatePeer
} }
func (device *Device) NewPeer(pk NoisePublicKey) *Peer { func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
@ -35,6 +32,7 @@ func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
peer.mutex.Lock() peer.mutex.Lock()
peer.device = device peer.device = device
peer.keyPairs.Init() peer.keyPairs.Init()
peer.mac.Init(pk)
peer.queueOutbound = make(chan *OutboundWorkQueueElement, OutboundQueueSize) peer.queueOutbound = make(chan *OutboundWorkQueueElement, OutboundQueueSize)
// map public key // map public key
@ -53,11 +51,6 @@ func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
handshake.mutex.Lock() handshake.mutex.Lock()
handshake.remoteStatic = pk handshake.remoteStatic = pk
handshake.precomputedStaticStatic = device.privateKey.sharedSecret(handshake.remoteStatic) handshake.precomputedStaticStatic = device.privateKey.sharedSecret(handshake.remoteStatic)
// compute mac key
peer.macKey = blake2s.Sum256(append([]byte(WGLabelMAC1[:]), handshake.remoteStatic[:]...))
handshake.mutex.Unlock() handshake.mutex.Unlock()
peer.mutex.Unlock() peer.mutex.Unlock()

View file

@ -24,6 +24,10 @@ type OutboundWorkQueueElement struct {
keyPair *KeyPair keyPair *KeyPair
} }
func (peer *Peer) HandshakeWorker(handshakeQueue []byte) {
}
func (device *Device) SendPacket(packet []byte) { func (device *Device) SendPacket(packet []byte) {
// lookup peer // lookup peer
@ -39,7 +43,7 @@ func (device *Device) SendPacket(packet []byte) {
peer = device.routingTable.LookupIPv6(dst) peer = device.routingTable.LookupIPv6(dst)
default: default:
device.logger.Println("unknown IP version") device.log.Debug.Println("receieved packet with unknown IP version")
return return
} }
@ -146,15 +150,13 @@ func (peer *Peer) RoutineOutboundNonceWorker() {
func (peer *Peer) RoutineSequential() { func (peer *Peer) RoutineSequential() {
for work := range peer.queueOutbound { for work := range peer.queueOutbound {
work.wg.Wait() work.wg.Wait()
// check if dropped ("ghost packet")
if work.packet == nil { if work.packet == nil {
continue continue
} }
if peer.endpoint == nil {
// continue
}
peer.device.conn.WriteToUDP(work.packet, peer.endpoint)
} }
} }