- Added last_minute_handshake_guard and reverted keypair changes.
- Added comment explaining the state of Go in releation to handling
cryptographic state in memory.
- Decreased logging level of netsh test
import (
"crypto/cipher"
- "golang.org/x/crypto/chacha20poly1305"
- "reflect"
"sync"
"time"
)
-type safeAEAD struct {
- mutex sync.RWMutex
- aead cipher.AEAD
-}
-
-func (con *safeAEAD) clear() {
- // TODO: improve handling of key material
- con.mutex.Lock()
- if con.aead != nil {
- val := reflect.ValueOf(con.aead)
- elm := val.Elem()
- typ := elm.Type()
- elm.Set(reflect.Zero(typ))
- con.aead = nil
- }
- con.mutex.Unlock()
-}
-
-func (con *safeAEAD) setKey(key *[chacha20poly1305.KeySize]byte) {
- // TODO: improve handling of key material
- con.aead, _ = chacha20poly1305.New(key[:])
-}
+/* Due to limitations in Go and /x/crypto there is currently
+ * no way to ensure that key material is securely ereased in memory.
+ *
+ * Since this may harm the forward secrecy property,
+ * we plan to resolve this issue; whenever Go allows us to do so.
+ */
type KeyPair struct {
- send safeAEAD
- receive safeAEAD
+ send cipher.AEAD
+ receive cipher.AEAD
replayFilter ReplayFilter
sendNonce uint64
isInitiator bool
}
func (device *Device) DeleteKeyPair(key *KeyPair) {
- key.send.clear()
- key.receive.clear()
device.indices.Delete(key.localIndex)
}
// create AEAD instances
keyPair := new(KeyPair)
- keyPair.send.setKey(&sendKey)
- keyPair.receive.setKey(&recvKey)
+ keyPair.send, _ = chacha20poly1305.New(sendKey[:])
+ keyPair.receive, _ = chacha20poly1305.New(recvKey[:])
setZero(sendKey[:])
setZero(recvKey[:])
// rotate key pairs
kp := &peer.keyPairs
- func() {
- kp.mutex.Lock()
- defer kp.mutex.Unlock()
- // TODO: Adapt kernel behavior noise.c:161
- if isInitiator {
- if kp.previous != nil {
- device.DeleteKeyPair(kp.previous)
- kp.previous = nil
- }
-
- if kp.next != nil {
- kp.previous = kp.next
- kp.next = keyPair
- } else {
- kp.previous = kp.current
- kp.current = keyPair
- signalSend(peer.signal.newKeyPair) // TODO: This more places (after confirming the key)
- }
+ kp.mutex.Lock()
- } else {
+ // TODO: Adapt kernel behavior noise.c:161
+ if isInitiator {
+ if kp.previous != nil {
+ device.DeleteKeyPair(kp.previous)
+ kp.previous = nil
+ }
+
+ if kp.next != nil {
+ kp.previous = kp.next
kp.next = keyPair
- kp.previous = nil // TODO: Discuss why
+ } else {
+ kp.previous = kp.current
+ kp.current = keyPair
+ signalSend(peer.signal.newKeyPair) // TODO: This more places (after confirming the key)
}
- }()
+
+ } else {
+ kp.next = keyPair
+ kp.previous = nil
+ }
+ kp.mutex.Unlock()
return keyPair
}
stop chan struct{} // (size 0) : close to stop all goroutines for peer
}
timer struct {
+ // state related to WireGuard timers
+
keepalivePersistent *time.Timer // set for persistent keepalives
keepalivePassive *time.Timer // set upon recieving messages
newHandshake *time.Timer // begin a new handshake (after Keepalive + RekeyTimeout)
pendingNewHandshake bool
pendingZeroAllKeys bool
- needAnotherKeepalive bool
+ needAnotherKeepalive bool
+ sendLastMinuteHandshake bool
}
queue struct {
nonce chan *QueueOutboundElement // nonce / pre-handshake queue
counter := elem.packet[MessageTransportOffsetCounter:MessageTransportOffsetContent]
content := elem.packet[MessageTransportOffsetContent:]
- // decrypt with key-pair
+ // decrypt and release to consumer
+ var err error
copy(nonce[4:], counter)
elem.counter = binary.LittleEndian.Uint64(counter)
- elem.keyPair.receive.mutex.RLock()
- if elem.keyPair.receive.aead == nil {
- // very unlikely (the key was deleted during queuing)
+ elem.packet, err = elem.keyPair.receive.Open(
+ elem.buffer[:0],
+ nonce[:],
+ content,
+ nil,
+ )
+ if err != nil {
elem.Drop()
- } else {
- var err error
- elem.packet, err = elem.keyPair.receive.aead.Open(
- elem.buffer[:0],
- nonce[:],
- content,
- nil,
- )
- if err != nil {
- elem.Drop()
- }
}
-
- elem.keyPair.receive.mutex.RUnlock()
elem.mutex.Unlock()
}
}
case MessageResponseType:
- logDebug.Println("Process response")
-
// unmarshal
var msg MessageResponse
continue
}
+ logDebug.Println("Received handshake initation from", peer)
+
peer.TimerEphemeralKeyCreated()
// update timers
}
}
- // encrypt content (append to header)
+ // encrypt content and release to consumer
binary.LittleEndian.PutUint64(nonce[4:], elem.nonce)
- elem.keyPair.send.mutex.RLock()
- if elem.keyPair.send.aead == nil {
- // very unlikely (the key was deleted during queuing)
- elem.Drop()
- } else {
- elem.packet = elem.keyPair.send.aead.Seal(
- header,
- nonce[:],
- elem.packet,
- nil,
- )
- }
+ elem.packet = elem.keyPair.send.Seal(
+ header,
+ nonce[:],
+ elem.packet,
+ nil,
+ )
elem.mutex.Unlock()
- elem.keyPair.send.mutex.RUnlock()
-
- // refresh key if necessary
-
- elem.peer.KeepKeyFreshSending()
}
}
}
netns1="wg-test-$$-1"
netns2="wg-test-$$-2"
program="../wireguard-go"
-export LOG_LEVEL="debug"
+export LOG_LEVEL="error"
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
pp() { pretty "" "$*"; "$@"; }
\r
/* Called when a new authenticated message has been recevied\r
*\r
+ * NOTE: Not thread safe (called by sequential receiver)\r
*/\r
func (peer *Peer) KeepKeyFreshReceiving() {\r
- // TODO: Add a guard, clear on handshake complete (clear in TimerHandshakeComplete)\r
+ if peer.timer.sendLastMinuteHandshake {\r
+ return\r
+ }\r
kp := peer.keyPairs.Current()\r
if kp == nil {\r
return\r
nonce := atomic.LoadUint64(&kp.sendNonce)\r
send := nonce > RekeyAfterMessages || time.Now().Sub(kp.created) > RekeyAfterTimeReceiving\r
if send {\r
+ // do a last minute attempt at initiating a new handshake\r
signalSend(peer.signal.handshakeBegin)\r
+ peer.timer.sendLastMinuteHandshake = true\r
}\r
}\r
\r
\r
case <-peer.signal.handshakeCompleted:\r
<-timeout.C\r
+ peer.timer.sendLastMinuteHandshake = false\r
break AttemptHandshakes\r
\r
case <-peer.signal.handshakeReset:\r