diff --git a/src/helper_test.go b/src/helper_test.go index 6d85771..3838a7c 100644 --- a/src/helper_test.go +++ b/src/helper_test.go @@ -18,8 +18,8 @@ func (tun *DummyTUN) Name() string { return tun.name } -func (tun *DummyTUN) MTU() int { - return tun.mtu +func (tun *DummyTUN) MTU() (int, error) { + return tun.mtu, nil } func (tun *DummyTUN) Write(d []byte) (int, error) { diff --git a/src/macs_device.go b/src/macs.go similarity index 62% rename from src/macs_device.go rename to src/macs.go index 68181e6..841ef31 100644 --- a/src/macs_device.go +++ b/src/macs.go @@ -1,10 +1,9 @@ package main import ( - "crypto/cipher" "crypto/hmac" "crypto/rand" - "github.com/aead/chacha20poly1305" // Needed for XChaCha20Poly1305, TODO: + "errors" "golang.org/x/crypto/blake2s" "net" "sync" @@ -17,9 +16,22 @@ type MACStateDevice struct { secret [blake2s.Size]byte keyMAC1 [blake2s.Size]byte keyMAC2 [blake2s.Size]byte - xaead cipher.AEAD } +type MACStatePeer struct { + mutex sync.RWMutex + cookieSet time.Time + cookie [blake2s.Size128]byte + lastMAC1 [blake2s.Size128]byte + keyMAC1 [blake2s.Size]byte + keyMAC2 [blake2s.Size]byte +} + +/* Methods for verifing MAC fields + * and creating/consuming cookies replies + * (per device) + */ + func (state *MACStateDevice) Init(pk NoisePublicKey) { state.mutex.Lock() defer state.mutex.Unlock() @@ -38,7 +50,6 @@ func (state *MACStateDevice) Init(pk NoisePublicKey) { hsh.Sum(state.keyMAC2[:0]) }() - state.xaead, _ = chacha20poly1305.NewXCipher(state.keyMAC2[:]) state.refreshed = time.Time{} } @@ -90,7 +101,10 @@ func (state *MACStateDevice) CheckMAC2(msg []byte, addr *net.UDPAddr) bool { return hmac.Equal(mac2[:], msg[start:]) } -func (device *Device) CreateMessageCookieReply(msg []byte, receiver uint32, addr *net.UDPAddr) (*MessageCookieReply, error) { +func (device *Device) CreateMessageCookieReply( + msg []byte, receiver uint32, addr *net.UDPAddr, +) (*MessageCookieReply, error) { + state := &device.mac state.mutex.RLock() @@ -137,7 +151,15 @@ func (device *Device) CreateMessageCookieReply(msg []byte, receiver uint32, addr state.mutex.RUnlock() return nil, err } - state.xaead.Seal(reply.Cookie[:0], reply.Nonce[:], cookie[:], mac1) + + XChaCha20Poly1305Encrypt( + reply.Cookie[:0], + &reply.Nonce, + cookie[:], + mac1, + &state.keyMAC2, + ) + state.mutex.RUnlock() return reply, nil } @@ -163,7 +185,14 @@ func (device *Device) ConsumeMessageCookieReply(msg *MessageCookieReply) bool { state.mutex.Lock() defer state.mutex.Unlock() - _, err := state.xaead.Open(cookie[:0], msg.Nonce[:], msg.Cookie[:], state.lastMAC1[:]) + _, err := XChaCha20Poly1305Decrypt( + cookie[:0], + &msg.Nonce, + msg.Cookie[:], + state.lastMAC1[:], + &state.keyMAC2, + ) + if err != nil { return false } @@ -171,3 +200,69 @@ func (device *Device) ConsumeMessageCookieReply(msg *MessageCookieReply) bool { state.cookie = cookie return true } + +/* Methods for generating the MAC fields + * (per peer) + */ + +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]) + }() + + func() { + hsh, _ := blake2s.New256(nil) + hsh.Write([]byte(WGLabelCookie)) + hsh.Write(pk[:]) + hsh.Sum(state.keyMAC2[:0]) + }() + + 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(mac1[:0]) + }() + copy(state.lastMAC1[:], mac1) + + // 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]) + }() +} diff --git a/src/macs_peer.go b/src/macs_peer.go deleted file mode 100644 index 16a7a87..0000000 --- a/src/macs_peer.go +++ /dev/null @@ -1,83 +0,0 @@ -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 - keyMAC2 [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]) - }() - - func() { - hsh, _ := blake2s.New256(nil) - hsh.Write([]byte(WGLabelCookie)) - hsh.Write(pk[:]) - hsh.Sum(state.keyMAC2[:0]) - }() - - state.xaead, _ = chacha20poly1305.NewXCipher(state.keyMAC2[:]) - 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]) - }() -} diff --git a/src/ratelimiter.go b/src/ratelimiter.go index a9f719b..4f8227e 100644 --- a/src/ratelimiter.go +++ b/src/ratelimiter.go @@ -1,9 +1,8 @@ package main -/* Implementation of the ratelimited form the linux kernel version - * - * - * +/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ + +/* This file contains a port of the ratelimited from the linux kernel version */ import ( diff --git a/src/replay.go b/src/replay.go index 49c7e08..5d42860 100644 --- a/src/replay.go +++ b/src/replay.go @@ -1,5 +1,7 @@ package main +/* Copyright (C) 2015-2017 Jason A. Donenfeld . All Rights Reserved. */ + /* Implementation of RFC6479 * https://tools.ietf.org/html/rfc6479 * diff --git a/src/xchacha20.go b/src/xchacha20.go new file mode 100644 index 0000000..5d963e0 --- /dev/null +++ b/src/xchacha20.go @@ -0,0 +1,169 @@ +// Copyright (c) 2016 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the LICENSE file. + +package main + +import ( + "encoding/binary" + "golang.org/x/crypto/chacha20poly1305" +) + +func HChaCha20(out *[32]byte, nonce []byte, key *[32]byte) { + + v00 := uint32(0x61707865) + v01 := uint32(0x3320646e) + v02 := uint32(0x79622d32) + v03 := uint32(0x6b206574) + + v04 := binary.LittleEndian.Uint32(key[0:]) + v05 := binary.LittleEndian.Uint32(key[4:]) + v06 := binary.LittleEndian.Uint32(key[8:]) + v07 := binary.LittleEndian.Uint32(key[12:]) + v08 := binary.LittleEndian.Uint32(key[16:]) + v09 := binary.LittleEndian.Uint32(key[20:]) + v10 := binary.LittleEndian.Uint32(key[24:]) + v11 := binary.LittleEndian.Uint32(key[28:]) + v12 := binary.LittleEndian.Uint32(nonce[0:]) + v13 := binary.LittleEndian.Uint32(nonce[4:]) + v14 := binary.LittleEndian.Uint32(nonce[8:]) + v15 := binary.LittleEndian.Uint32(nonce[12:]) + + for i := 0; i < 20; i += 2 { + v00 += v04 + v12 ^= v00 + v12 = (v12 << 16) | (v12 >> 16) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 12) | (v04 >> 20) + v00 += v04 + v12 ^= v00 + v12 = (v12 << 8) | (v12 >> 24) + v08 += v12 + v04 ^= v08 + v04 = (v04 << 7) | (v04 >> 25) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 16) | (v13 >> 16) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 12) | (v05 >> 20) + v01 += v05 + v13 ^= v01 + v13 = (v13 << 8) | (v13 >> 24) + v09 += v13 + v05 ^= v09 + v05 = (v05 << 7) | (v05 >> 25) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 16) | (v14 >> 16) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 12) | (v06 >> 20) + v02 += v06 + v14 ^= v02 + v14 = (v14 << 8) | (v14 >> 24) + v10 += v14 + v06 ^= v10 + v06 = (v06 << 7) | (v06 >> 25) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 16) | (v15 >> 16) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 12) | (v07 >> 20) + v03 += v07 + v15 ^= v03 + v15 = (v15 << 8) | (v15 >> 24) + v11 += v15 + v07 ^= v11 + v07 = (v07 << 7) | (v07 >> 25) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 16) | (v15 >> 16) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 12) | (v05 >> 20) + v00 += v05 + v15 ^= v00 + v15 = (v15 << 8) | (v15 >> 24) + v10 += v15 + v05 ^= v10 + v05 = (v05 << 7) | (v05 >> 25) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 16) | (v12 >> 16) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 12) | (v06 >> 20) + v01 += v06 + v12 ^= v01 + v12 = (v12 << 8) | (v12 >> 24) + v11 += v12 + v06 ^= v11 + v06 = (v06 << 7) | (v06 >> 25) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 16) | (v13 >> 16) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 12) | (v07 >> 20) + v02 += v07 + v13 ^= v02 + v13 = (v13 << 8) | (v13 >> 24) + v08 += v13 + v07 ^= v08 + v07 = (v07 << 7) | (v07 >> 25) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 16) | (v14 >> 16) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 12) | (v04 >> 20) + v03 += v04 + v14 ^= v03 + v14 = (v14 << 8) | (v14 >> 24) + v09 += v14 + v04 ^= v09 + v04 = (v04 << 7) | (v04 >> 25) + } + + binary.LittleEndian.PutUint32(out[0:], v00) + binary.LittleEndian.PutUint32(out[4:], v01) + binary.LittleEndian.PutUint32(out[8:], v02) + binary.LittleEndian.PutUint32(out[12:], v03) + binary.LittleEndian.PutUint32(out[16:], v12) + binary.LittleEndian.PutUint32(out[20:], v13) + binary.LittleEndian.PutUint32(out[24:], v14) + binary.LittleEndian.PutUint32(out[28:], v15) +} + +func XChaCha20Poly1305Encrypt( + dst []byte, + nonceFull *[24]byte, + plaintext []byte, + additionalData []byte, + key *[chacha20poly1305.KeySize]byte, +) []byte { + var nonce [chacha20poly1305.NonceSize]byte + var derivedKey [chacha20poly1305.KeySize]byte + HChaCha20(&derivedKey, nonceFull[:16], key) + aead, _ := chacha20poly1305.New(derivedKey[:]) + copy(nonce[4:], nonceFull[16:]) + return aead.Seal(dst, nonce[:], plaintext, additionalData) +} + +func XChaCha20Poly1305Decrypt( + dst []byte, + nonceFull *[24]byte, + plaintext []byte, + additionalData []byte, + key *[chacha20poly1305.KeySize]byte, +) ([]byte, error) { + var nonce [chacha20poly1305.NonceSize]byte + var derivedKey [chacha20poly1305.KeySize]byte + HChaCha20(&derivedKey, nonceFull[:16], key) + aead, _ := chacha20poly1305.New(derivedKey[:]) + copy(nonce[4:], nonceFull[16:]) + return aead.Open(dst, nonce[:], plaintext, additionalData) +} diff --git a/src/xchacha20_test.go b/src/xchacha20_test.go new file mode 100644 index 0000000..0f41cf8 --- /dev/null +++ b/src/xchacha20_test.go @@ -0,0 +1,96 @@ +package main + +import ( + "encoding/hex" + "testing" +) + +type XChaCha20Test struct { + Nonce string + Key string + PT string + CT string +} + +func TestXChaCha20(t *testing.T) { + + tests := []XChaCha20Test{ + { + Nonce: "000000000000000000000000000000000000000000000000", + Key: "0000000000000000000000000000000000000000000000000000000000000000", + PT: "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + CT: "789e9689e5208d7fd9e1f3c5b5341f48ef18a13e418998addadd97a3693a987f8e82ecd5c1433bfed1af49750c0f1ff29c4174a05b119aa3a9e8333812e0c0feb1299c5949d895ee01dbf50f8395dd84", + }, + { + Nonce: "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", + Key: "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", + PT: "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", + CT: "e1a046aa7f71e2af8b80b6408b2fd8d3a350278cde79c94d9efaa475e1339b3dd490127b", + }, + { + Nonce: "d9a8213e8a697508805c2c171ad54487ead9e3e02d82d5bc", + Key: "979196dbd78526f2f584f7534db3f5824d8ccfa858ca7e09bdd3656ecd36033c", + PT: "43cc6d624e451bbed952c3e071dc6c03392ce11eb14316a94b2fdc98b22fedea", + CT: "53c1e8bef2dbb8f2505ec010a7afe21d5a8e6dd8f987e4ea1a2ed5dfbc844ea400db34496fd2153526c6e87c36694200", + }, + } + + for _, test := range tests { + + nonce, err := hex.DecodeString(test.Nonce) + if err != nil { + panic(err) + } + + key, err := hex.DecodeString(test.Key) + if err != nil { + panic(err) + } + + pt, err := hex.DecodeString(test.PT) + if err != nil { + panic(err) + } + + func() { + var nonceArray [24]byte + var keyArray [32]byte + copy(nonceArray[:], nonce) + copy(keyArray[:], key) + + // test encryption + + ct := XChaCha20Poly1305Encrypt( + nil, + &nonceArray, + pt, + nil, + &keyArray, + ) + ctHex := hex.EncodeToString(ct) + if ctHex != test.CT { + t.Fatal("encryption failed, expected:", test.CT, "got", ctHex) + } + + // test decryption + + ptp, err := XChaCha20Poly1305Decrypt( + nil, + &nonceArray, + ct, + nil, + &keyArray, + ) + if err != nil { + t.Fatal(err) + } + + ptHex := hex.EncodeToString(ptp) + if ptHex != test.PT { + t.Fatal("decryption failed, expected:", test.PT, "got", ptHex) + } + }() + + } + +}