human memorized passphrases to be used
* Randomize ports usage
* Fragmentize packets, noise the traffic
-* Indistinguishable from noise handshake packets
govpn.Noncediff = *nonceDiff
id := govpn.IDDecode(*IDRaw)
+ govpn.PeersInitDummy(id)
key := govpn.KeyRead(*keyPath)
if id == nil {
panic("ID is not specified")
}
udpPktData = udpBuf[:udpPkt.Size]
- if govpn.IsValidHandshakePkt(udpPktData) {
+ if peer == nil {
if udpPkt.Addr.String() != remote.String() {
udpReady <- struct{}{}
log.Println("Unknown handshake message")
continue
}
- if p := handshake.Client(conn, key, udpPktData); p != nil {
+ if govpn.IDsCache.Find(udpPktData) == nil {
+ log.Println("Invalid identity in handshake packet")
+ udpReady <- struct{}{}
+ continue
+ }
+ if p := handshake.Client(id, conn, key, udpPktData); p != nil {
log.Println("Handshake completed")
if firstUpCall {
go govpn.ScriptCall(*upPath, *ifaceName)
var udpPkt *govpn.UDPPkt
var udpPktData []byte
var ethEvent EthEvent
+ var peerId *govpn.PeerId
+ var handshakeProcessForce bool
ethSink := make(chan EthEvent)
log.Println(govpn.VersionGet())
}
udpPktData = udpBuf[:udpPkt.Size]
addr = udpPkt.Addr.String()
- if govpn.IsValidHandshakePkt(udpPktData) {
+ handshakeProcessForce = false
+ HandshakeProcess:
+ if _, exists = peers[addr]; handshakeProcessForce || !exists {
+ peerId = govpn.IDsCache.Find(udpPktData)
+ if peerId == nil {
+ log.Println("Unknown identity from", addr)
+ udpReady <- struct{}{}
+ continue
+ }
state, exists = states[addr]
if !exists {
state = govpn.HandshakeNew(udpPkt.Addr)
states[addr] = state
}
- peer = state.Server(conn, udpPktData)
+ peer = state.Server(peerId, conn, udpPktData)
if peer != nil {
log.Println("Peer handshake finished", peer)
if _, exists = peers[addr]; exists {
}()
}
}
- udpReady <- struct{}{}
+ if !handshakeProcessForce {
+ udpReady <- struct{}{}
+ }
continue
}
peerState, exists = peers[addr]
udpReady <- struct{}{}
continue
}
- peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady)
+ // If it fails during processing, then try to work with it
+ // as with handshake packet
+ if !peerState.peer.UDPProcess(udpPktData, peerState.tap, udpReady) {
+ handshakeProcessForce = true
+ goto HandshakeProcess
+ }
}
}
}
@item Packet overhead
24 bytes per packet
@item Handshake overhead
-4 UDP (2 from client, 2 from server) packets, 240 bytes total payload
+4 UDP (2 from client, 2 from server) packets, 200 bytes total payload
@end table
@menu
AUTH(ENCn(SERIAL) + ENC(KEY, ENCn(SERIAL), DATA))
@end verbatim
-Each transport message is indistinguishable from pseudo random noise.
+All transport and handshake messages are indistinguishable from
+pseudo random noise.
@code{SERIAL} is message's serial number. Odds are reserved for
client(→server) messages, evens for server(→client) messages.
@verbatiminclude handshake.utxt
+Each handshake message ends with so called @code{IDtag}: it is an XTEA
+encrypted first 64 bits of each message with client's identity as a key.
+It is used to transmit identity and to mark packet as handshake message.
+Server can determine used identity by trying all possible known to him
+keys. It consumes resources, but XTEA is rather fast algorithm and
+handshake messages checking is seldom enough event.
+
@enumerate
@item
client generates @code{CPubKey}, random 64bit @code{R} that is used as a
-nonce for encryption, and an encrypted @code{R} with XTEA, where the key
-equals to client's identity
+nonce for encryption
@item
-@verb{|R + enc(PSK, R, CPubKey) + xtea(ID, R) + NULL + NULLs -> Server|} [65 bytes]
+@verb{|R + enc(PSK, R, CPubKey) + IDtag -> Server|} [48 bytes]
@item
server remembers clients address, decrypt @code{CPubKey}, generates
@code{SPrivKey}/@code{SPubKey}, computes common shared key @code{K}
number @code{RS} and 256bit random @code{SS}. PSK-encryption uses
incremented @code{R} (from previous message) for nonce
@item
-@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + NULLs -> Client|} [88 bytes]
+@verb{|enc(PSK, R+1, SPubKey) + enc(K, R, RS + SS) + IDtag -> Client|} [80 bytes]
@item
client decrypt @code{SPubKey}, computes @code{K}, decrypts @code{RS},
@code{SS} with key @code{K}, remembers @code{SS}, generates 64bit random
number @code{RC} and 256bit random @code{SC},
@item
-@verb{|enc(K, R+1, RS + RC + SC) + NULLs -> Server|} [64 bytes]
+@verb{|enc(K, R+1, RS + RC + SC) + IDtag -> Server|} [56 bytes]
@item
server decrypt @code{RS}, @code{RC}, @code{SC} with key @code{K},
compares @code{RS} with it's own one send before, computes final main
encryption key @code{S = SS XOR SC}
@item
-@verb{|ENC(K, 0, RC) + NULLs -> Client|} [24 bytes]
+@verb{|ENC(K, 0, RC) + IDtag -> Client|} [16 bytes]
@item
server switches to the new client
@item
computes final main encryption key @code{S}
@end enumerate
-Where PSK is 256bit pre-shared key, @code{NULLs} are 16 null-bytes.
-@code{R*} are required for handshake randomization and two-way
-authentication. K key is used only during handshake. @code{NULLs} are
-required to differentiate common transport protocol messages from
-handshake ones. DH public keys can be trivially derived from private
-ones.
+Where PSK is 256bit pre-shared key. @code{R*} are required for handshake
+randomization and two-way authentication. K key is used only during
+handshake. DH public keys can be trivially derived from private ones.
@node Reporting bugs
@unnumbered Reporting bugs
Client -> Client : R=rand(64bit)
Client -> Client : CPrivKey=rand(256bit)
-Client -> Server : R, enc(PSK, R, CPubKey), xtea(ID, R)
+Client -> Server : R, enc(PSK, R, CPubKey)
Server -> Server : SPrivKey=rand(256bit)
Server -> Server : K=DH(SPrivKey, CPubKey)
Server -> Server : RS=rand(64bit)
"time"
"golang.org/x/crypto/curve25519"
- "golang.org/x/crypto/poly1305"
"golang.org/x/crypto/salsa20"
"golang.org/x/crypto/salsa20/salsa"
"golang.org/x/crypto/xtea"
return k
}
-// Check if it is valid handshake-related message.
-// Minimal size and last 16 zero bytes.
-func IsValidHandshakePkt(pkt []byte) bool {
- if len(pkt) < 24 {
- return false
- }
- for i := len(pkt) - poly1305.TagSize; i < len(pkt); i++ {
- if pkt[i] != '\x00' {
- return false
- }
- }
- return true
-}
-
// Zero handshake's memory state
func (h *Handshake) Zero() {
- sliceZero(h.rNonce[:])
- sliceZero(h.dhPriv[:])
- sliceZero(h.key[:])
+ if h.rNonce != nil {
+ sliceZero(h.rNonce[:])
+ }
+ if h.dhPriv != nil {
+ sliceZero(h.dhPriv[:])
+ }
+ if h.key != nil {
+ sliceZero(h.key[:])
+ }
if h.rServer != nil {
sliceZero(h.rServer[:])
}
return &state
}
+// Generate ID tag from client identification and data.
+func idTag(id *PeerId, data []byte) []byte {
+ ciph, err := xtea.NewCipher(id[:])
+ if err != nil {
+ panic(err)
+ }
+ enc := make([]byte, xtea.BlockSize)
+ ciph.Encrypt(enc, data[:xtea.BlockSize])
+ return enc
+}
+
// Start handshake's procedure from the client.
// It is the entry point for starting the handshake procedure.
// You have to specify outgoing conn address, remote's addr address,
}
enc := make([]byte, 32)
salsa20.XORKeyStream(enc, dhPub[:], state.rNonce[:], key)
-
- ciph, err := xtea.NewCipher(id[:])
- if err != nil {
- panic(err)
- }
- rEnc := make([]byte, xtea.BlockSize)
- ciph.Encrypt(rEnc, state.rNonce[:])
-
data := append(state.rNonce[:], enc...)
- data = append(data, rEnc...)
- data = append(data, '\x00')
- data = append(data, make([]byte, poly1305.TagSize)...)
-
+ data = append(data, idTag(id, state.rNonce[:])...)
if _, err := conn.WriteTo(data, addr); err != nil {
panic(err)
}
// Process handshake message on the server side.
// This function is intended to be called on server's side.
-// Our outgoing conn connection and received data are required.
+// Client identity, our outgoing conn connection and
+// received data are required.
// If this is the final handshake message, then new Peer object
// will be created and used as a transport. If no mutually
// authenticated Peer is ready, then return nil.
-func (h *Handshake) Server(conn *net.UDPConn, data []byte) *Peer {
- switch len(data) {
- case 65: // R + ENC(PSK, dh_client_pub) + xtea(ID, R) + NULL + NULLs
- if h.rNonce != nil {
- log.Println("Invalid handshake stage from", h.addr)
- return nil
- }
-
- // Try to determine client's ID
- id := IDsCache.Find(data[:8], data[8+32:8+32+8])
- if id == nil {
- log.Println("Unknown identity from", h.addr)
- return nil
- }
+func (h *Handshake) Server(id *PeerId, conn *net.UDPConn, data []byte) *Peer {
+ // R + ENC(PSK, dh_client_pub) + IDtag
+ if len(data) == 48 && h.rNonce == nil {
key := KeyRead(path.Join(PeersPath, id.String(), "key"))
h.Id = *id
// Send that to client
if _, err := conn.WriteTo(
- append(encPub,
- append(encRs, make([]byte, poly1305.TagSize)...)...), h.addr); err != nil {
+ append(encPub, append(encRs, idTag(id, encPub)...)...), h.addr); err != nil {
panic(err)
}
h.LastPing = time.Now()
- case 64: // ENC(K, RS + RC + SC) + NULLs
- if (h.rNonce == nil) || (h.rClient != nil) {
- log.Println("Invalid handshake stage from", h.addr)
- return nil
- }
-
+ } else
+ // ENC(K, RS + RC + SC) + IDtag
+ if len(data) == 56 && h.rClient == nil {
// Decrypted Rs compare rServer
decRs := make([]byte, 8+8+32)
salsa20.XORKeyStream(decRs, data[:8+8+32], h.rNonceNext(), h.key)
// Send final answer to client
enc := make([]byte, 8)
salsa20.XORKeyStream(enc, decRs[8:8+8], make([]byte, 8), h.key)
- if _, err := conn.WriteTo(append(enc, make([]byte, poly1305.TagSize)...), h.addr); err != nil {
+ if _, err := conn.WriteTo(append(enc, idTag(id, enc)...), h.addr); err != nil {
panic(err)
}
peer := newPeer(h.addr, h.Id, 0, keyFromSecrets(h.sServer[:], decRs[8+8:]))
h.LastPing = time.Now()
return peer
- default:
+ } else {
log.Println("Invalid handshake message from", h.addr)
}
return nil
// Process handshake message on the client side.
// This function is intended to be called on client's side.
-// Our outgoing conn connection, authentication key and received data
-// are required. Client does not work with identities, as he is the
-// only one, so key is a requirement.
+// Our outgoing conn connection, authentication
+// key and received data are required.
// If this is the final handshake message, then new Peer object
// will be created and used as a transport. If no mutually
// authenticated Peer is ready, then return nil.
-func (h *Handshake) Client(conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer {
+func (h *Handshake) Client(id *PeerId, conn *net.UDPConn, key *[KeySize]byte, data []byte) *Peer {
switch len(data) {
- case 88: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + NULLs
+ case 80: // ENC(PSK, dh_server_pub) + ENC(K, RS + SS) + IDtag
if h.key != nil {
log.Println("Invalid handshake stage from", h.addr)
return nil
append(h.rClient[:], h.sClient[:]...)...), h.rNonceNext(), h.key)
// Send that to server
- if _, err := conn.WriteTo(append(encRs, make([]byte, poly1305.TagSize)...), h.addr); err != nil {
+ if _, err := conn.WriteTo(append(encRs, idTag(id, encRs)...), h.addr); err != nil {
panic(err)
}
h.LastPing = time.Now()
- case 24: // ENC(K, RC) + NULLs
+ case 16: // ENC(K, RC) + IDtag
if h.key == nil {
log.Println("Invalid handshake stage from", h.addr)
return nil
}()
}
+// Initialize dummy cache for client-side usage. It will consist only
+// of single key.
+func PeersInitDummy(id *PeerId) {
+ IDsCache = make(map[PeerId]*xtea.Cipher)
+ cipher, err := xtea.NewCipher(id[:])
+ if err != nil {
+ panic(err)
+ }
+ IDsCache[*id] = cipher
+}
+
// Refresh IDsCache: remove disappeared keys, add missing ones with
// initialized ciphers.
func (cc cipherCache) refresh() {
}
// Try to find peer's identity (that equals to an encryption key)
-// by providing cipher and plain texts.
-func (cc cipherCache) Find(plaintext, ciphertext []byte) *PeerId {
+// by taking first blocksize sized bytes from data at the beginning
+// as plaintext and last bytes as cyphertext.
+func (cc cipherCache) Find(data []byte) *PeerId {
+ if len(data) < xtea.BlockSize*2 {
+ return nil
+ }
buf := make([]byte, xtea.BlockSize)
cipherCacheLock.RLock()
for pid, cipher := range cc {
- cipher.Decrypt(buf, ciphertext)
- if subtle.ConstantTimeCompare(buf, plaintext) == 1 {
+ cipher.Decrypt(buf, data[len(data)-xtea.BlockSize:])
+ if subtle.ConstantTimeCompare(buf, data[:xtea.BlockSize]) == 1 {
ppid := PeerId(pid)
cipherCacheLock.RUnlock()
return &ppid