}
case "listen_port":
- _, err := fmt.Sscanf(value, "%ud", &device.listenPort)
+ _, err := fmt.Sscanf(value, "%ud", &device.address.Port)
if err != nil {
return &IPCError{Code: ipcErrorInvalidPort}
}
--- /dev/null
+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
+)
+++ /dev/null
-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])
- }
-}
package main
import (
- "log"
"net"
"sync"
)
type Device struct {
mtu int
- source *net.UDPAddr // UDP source address
+ fwMark uint32
+ address *net.UDPAddr // UDP source address
conn *net.UDPConn // UDP "connection"
mutex sync.RWMutex
- peers map[NoisePublicKey]*Peer
- indices IndexTable
privateKey NoisePrivateKey
publicKey NoisePublicKey
- fwMark uint32
- listenPort uint16
routingTable RoutingTable
- logger log.Logger
+ indices IndexTable
+ log *Logger
queueWorkOutbound chan *OutboundWorkQueueElement
+ peers map[NoisePublicKey]*Peer
+ mac MacStateDevice
}
func (device *Device) SetPrivateKey(sk NoisePrivateKey) {
device.privateKey = sk
device.publicKey = sk.publicKey()
+ device.mac.Init(device.publicKey)
- // do precomputations
+ // do DH precomputations
for _, peer := range device.peers {
h := &peer.handshake
device.mutex.Lock()
defer device.mutex.Unlock()
+ device.log = NewLogger()
device.peers = make(map[NoisePublicKey]*Peer)
device.indices.Init()
- device.listenPort = 0
device.routingTable.Reset()
}
import (
"crypto/cipher"
"sync"
+ "time"
)
type KeyPair struct {
- recv cipher.AEAD
- recvNonce uint64
- send cipher.AEAD
- sendNonce uint64
+ recv cipher.AEAD
+ recvNonce uint64
+ send cipher.AEAD
+ sendNonce uint64
+ isInitiator bool
+ created time.Time
}
type KeyPairs struct {
--- /dev/null
+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
+}
--- /dev/null
+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
+}
--- /dev/null
+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])
+ }()
+}
--- /dev/null
+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)
+ }
+}
)
const (
- MessageInitiationType = 1
- MessageResponseType = 2
- MessageCookieResponseType = 3
- MessageTransportType = 4
+ MessageInitiationType = 1
+ MessageResponseType = 2
+ MessageCookieReplyType = 3
+ MessageTransportType = 4
+)
+
+const (
+ MessageInitiationSize = 148
+ MessageResponseSize = 92
)
/* Type is an 8-bit field, followed by 3 nul bytes,
* 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)
*
*/
Content []byte
}
+type MessageCookieReply struct {
+ Type uint32
+ Receiver uint32
+ Nonce [24]byte
+ Cookie [blake2s.Size128 + poly1305.TagSize]byte
+}
+
type Handshake struct {
state int
mutex sync.Mutex
}
}
+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) {
sk1, err := newPrivateKey()
assertNil(t, err)
}
}
-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) {
dev1 := newDevice(t)
import (
"errors"
- "golang.org/x/crypto/blake2s"
"net"
"sync"
"time"
keyPairs KeyPairs
handshake Handshake
device *Device
- macKey [blake2s.Size]byte // Hash(Label-Mac1 || publicKey)
- cookie []byte // cookie
- cookieExpire time.Time
queueInbound chan []byte
queueOutbound chan *OutboundWorkQueueElement
queueOutboundRouting chan []byte
+ mac MacStatePeer
}
func (device *Device) NewPeer(pk NoisePublicKey) *Peer {
peer.mutex.Lock()
peer.device = device
peer.keyPairs.Init()
+ peer.mac.Init(pk)
peer.queueOutbound = make(chan *OutboundWorkQueueElement, OutboundQueueSize)
// map public key
handshake.mutex.Lock()
handshake.remoteStatic = pk
handshake.precomputedStaticStatic = device.privateKey.sharedSecret(handshake.remoteStatic)
-
- // compute mac key
-
- peer.macKey = blake2s.Sum256(append([]byte(WGLabelMAC1[:]), handshake.remoteStatic[:]...))
-
handshake.mutex.Unlock()
peer.mutex.Unlock()
keyPair *KeyPair
}
+func (peer *Peer) HandshakeWorker(handshakeQueue []byte) {
+
+}
+
func (device *Device) SendPacket(packet []byte) {
// lookup peer
peer = device.routingTable.LookupIPv6(dst)
default:
- device.logger.Println("unknown IP version")
+ device.log.Debug.Println("receieved packet with unknown IP version")
return
}
func (peer *Peer) RoutineSequential() {
for work := range peer.queueOutbound {
work.wg.Wait()
-
- // check if dropped ("ghost packet")
-
if work.packet == nil {
continue
}
-
- //
-
+ if peer.endpoint == nil {
+ continue
+ }
+ peer.device.conn.WriteToUDP(work.packet, peer.endpoint)
}
}