From 99d303ac2739e65a02fbbc325b74ad6fcac63cc2 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 5 Jun 2015 15:58:00 +0200 Subject: Initial commit --- contrib/external-tests/go/main.go | 63 ++++++++++++++++ contrib/external-tests/haskell/Setup.hs | 2 + contrib/external-tests/haskell/cacophony-wg.cabal | 34 +++++++++ .../external-tests/haskell/src/Data/Time/TAI64.hs | 86 ++++++++++++++++++++++ contrib/external-tests/haskell/src/Main.hs | 81 ++++++++++++++++++++ contrib/external-tests/rust/.gitignore | 2 + contrib/external-tests/rust/Cargo.toml | 10 +++ contrib/external-tests/rust/src/main.rs | 74 +++++++++++++++++++ 8 files changed, 352 insertions(+) create mode 100644 contrib/external-tests/go/main.go create mode 100644 contrib/external-tests/haskell/Setup.hs create mode 100644 contrib/external-tests/haskell/cacophony-wg.cabal create mode 100644 contrib/external-tests/haskell/src/Data/Time/TAI64.hs create mode 100644 contrib/external-tests/haskell/src/Main.hs create mode 100644 contrib/external-tests/rust/.gitignore create mode 100644 contrib/external-tests/rust/Cargo.toml create mode 100644 contrib/external-tests/rust/src/main.rs (limited to 'contrib/external-tests') diff --git a/contrib/external-tests/go/main.go b/contrib/external-tests/go/main.go new file mode 100644 index 0000000..de7337b --- /dev/null +++ b/contrib/external-tests/go/main.go @@ -0,0 +1,63 @@ +/* Copyright 2015-2016 Jason A. Donenfeld . All Rights Reserved. */ + +package main + +import ( + "github.com/titanous/noise" + "net" + "time" + "bytes" + "crypto/rand" + "encoding/base64" + "encoding/binary" + "github.com/dchest/blake2s" +) + +func assert(exp bool) { + if !exp { + panic("Assertion failed.") + } +} + +func main() { + my_private, _ := base64.StdEncoding.DecodeString("WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=") + my_public, _ := base64.StdEncoding.DecodeString("K5sF9yESrSBsOXPd6TcpKNgqoy1Ik3ZFKl4FolzrRyI=") + preshared, _ := base64.StdEncoding.DecodeString("FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=") + their_public, _ := base64.StdEncoding.DecodeString("qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=") + cs := noise.NewCipherSuite(noise.DH25519, noise.CipherChaChaPoly, noise.HashBLAKE2s) + hs := noise.NewHandshakeState(noise.Config{CipherSuite: cs, Random: rand.Reader, Pattern: noise.HandshakeIK, Initiator: true, Prologue: []byte("WireGuard v0 zx2c4 Jason@zx2c4.com"), PresharedKey: preshared, StaticKeypair: noise.DHKey{Private: my_private, Public: my_public}, PeerStatic: their_public}) + conn, _ := net.Dial("udp", "test.wireguard.io:51820") + + now := time.Now() + tai64n := make([]byte, 12) + binary.BigEndian.PutUint64(tai64n[:], uint64(now.Unix())) + binary.BigEndian.PutUint32(tai64n[8:], uint32(now.UnixNano())) + initiation_packet := make([]byte, 5) + initiation_packet[0] = 1 /* Type: Initiation */ + binary.LittleEndian.PutUint32(initiation_packet[1:], 28) /* Sender index: 28 (arbitrary) */ + initiation_packet, _, _ = hs.WriteMessage(initiation_packet, tai64n) + hasher, _ := blake2s.New(&blake2s.Config{Size: 16, Key: preshared}) + hasher.Write(their_public) + hasher.Write(initiation_packet) + initiation_packet = append(initiation_packet, hasher.Sum(nil)[:16]...) + initiation_packet = append(initiation_packet, bytes.Repeat([]byte{ 0 }, 16)...) + conn.Write(initiation_packet) + + response_packet := make([]byte, 89) + conn.Read(response_packet) + assert(response_packet[0] == 2 /* Type: Response */) + their_index := binary.LittleEndian.Uint32(response_packet[1:]) + our_index := binary.LittleEndian.Uint32(response_packet[5:]) + assert(our_index == 28) + payload, send_cs, _, err := hs.ReadMessage(nil, response_packet[9:57]) + assert(len(payload) == 0 && err == nil) + + keepalive_packet := make([]byte, 13) + keepalive_packet[0] = 4 /* Type: Data */ + binary.LittleEndian.PutUint32(keepalive_packet[1:], their_index) + binary.LittleEndian.PutUint64(keepalive_packet[3:], 0) /* Nonce */ + keepalive_packet = send_cs.Encrypt(keepalive_packet, nil, nil) + conn.Write(keepalive_packet) + + conn.Close() +} diff --git a/contrib/external-tests/haskell/Setup.hs b/contrib/external-tests/haskell/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/contrib/external-tests/haskell/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/contrib/external-tests/haskell/cacophony-wg.cabal b/contrib/external-tests/haskell/cacophony-wg.cabal new file mode 100644 index 0000000..62e2485 --- /dev/null +++ b/contrib/external-tests/haskell/cacophony-wg.cabal @@ -0,0 +1,34 @@ +-- Initial cacophony-wg.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/ + +name: cacophony-wg +version: 0.1.0 +-- synopsis: +-- description: +license: PublicDomain +license-file: LICENSE +author: John Galt +maintainer: centromere@users.noreply.github.com +-- copyright: +-- category: +build-type: Simple +-- extra-source-files: +cabal-version: >=1.10 + +executable cacophony-wg + main-is: Main.hs + other-modules: + Data.Time.TAI64 + build-depends: + base >=4.8 && <4.9, + base16-bytestring, + base64-bytestring, + blake2, + bytestring, + cacophony, + cereal, + cryptonite, + network, + time + hs-source-dirs: src + default-language: Haskell2010 diff --git a/contrib/external-tests/haskell/src/Data/Time/TAI64.hs b/contrib/external-tests/haskell/src/Data/Time/TAI64.hs new file mode 100644 index 0000000..37a90e6 --- /dev/null +++ b/contrib/external-tests/haskell/src/Data/Time/TAI64.hs @@ -0,0 +1,86 @@ +module Data.Time.TAI64 ( + TAI64(..) + , TAI64N(..) + , TAI64NA(..) + , posixToTAI64 + , posixToTAI64N + , posixToTAI64NA + , getCurrentTAI64 + , getCurrentTAI64N + , getCurrentTAI64NA + , tAI64ToPosix + , tAI64NToPosix + , tAI64NAToPosix +) where + +import Data.Serialize +import Control.Monad +import Data.Word + +import Data.Time.Clock +import Data.Time.Clock.POSIX + +import Numeric + +data TAI64 = TAI64 + {-# UNPACK #-} !Word64 + deriving (Eq, Ord) + +data TAI64N = TAI64N + {-# UNPACK #-} !TAI64 + {-# UNPACK #-} !Word32 + deriving (Eq, Ord, Show) + +data TAI64NA = TAI64NA + {-# UNPACK #-} !TAI64N + {-# UNPACK #-} !Word32 + deriving (Eq, Ord, Show) + +instance Show TAI64 where + show (TAI64 t) = "TAI64 0x" ++ showHex t "" + +instance Serialize TAI64 where + put (TAI64 t) = putWord64be t + get = liftM TAI64 get + +instance Serialize TAI64N where + put (TAI64N t' nt) = put t' >> putWord32be nt + get = liftM2 TAI64N get get + +instance Serialize TAI64NA where + put (TAI64NA t' at) = put t' >> putWord32be at + get = liftM2 TAI64NA get get + + +posixToTAI64 :: POSIXTime -> TAI64 +posixToTAI64 = TAI64 . (2^62 +) . truncate . realToFrac + +posixToTAI64N :: POSIXTime -> TAI64N +posixToTAI64N pt = TAI64N t' ns where + t' = posixToTAI64 pt + ns = (`mod` 10^9) $ truncate (pts * 10**9) + pts = realToFrac pt + +posixToTAI64NA :: POSIXTime -> TAI64NA -- | PICOsecond precision +posixToTAI64NA pt = TAI64NA t' as where + t' = posixToTAI64N pt + as = (`mod` 10^9) $ truncate (pts * 10**18) + pts = realToFrac pt + +getCurrentTAI64 :: IO TAI64 +getCurrentTAI64N :: IO TAI64N +getCurrentTAI64NA :: IO TAI64NA +getCurrentTAI64 = liftM posixToTAI64 getPOSIXTime +getCurrentTAI64N = liftM posixToTAI64N getPOSIXTime +getCurrentTAI64NA = liftM posixToTAI64NA getPOSIXTime + +tAI64ToPosix :: TAI64 -> POSIXTime +tAI64ToPosix (TAI64 s) = fromRational . fromIntegral $ s - 2^62 + +tAI64NToPosix :: TAI64N -> POSIXTime +tAI64NToPosix (TAI64N t' n) = tAI64ToPosix t' + nanopart where + nanopart = fromRational $ (toRational $ 10**(-9)) * toRational n -- TODO: optimize? + +tAI64NAToPosix :: TAI64NA -> POSIXTime +tAI64NAToPosix (TAI64NA t' a) = tAI64NToPosix t' + attopart where + attopart = fromRational $ (toRational $ 10**(-18)) * toRational a diff --git a/contrib/external-tests/haskell/src/Main.hs b/contrib/external-tests/haskell/src/Main.hs new file mode 100644 index 0000000..f78305d --- /dev/null +++ b/contrib/external-tests/haskell/src/Main.hs @@ -0,0 +1,81 @@ +{-# LANGUAGE OverloadedStrings #-} +module Main where + +import Control.Applicative ((<$>)) +import Control.Concurrent.MVar +import Control.Monad (void) +import Data.ByteString.Char8 (pack, unpack, take, drop, replicate) +import Data.ByteString (ByteString) +import qualified Data.ByteString.Base16 as Hex +import qualified Data.ByteString.Base64 as B64 +import qualified Data.Serialize as S +import Prelude hiding (take, drop, replicate) +import System.Environment +import Network.Socket +import qualified Network.Socket.ByteString as NBS + +import Crypto.Hash.BLAKE2.BLAKE2s +import Crypto.Noise.Cipher +import Crypto.Noise.Cipher.ChaChaPoly1305 +import Crypto.Noise.Curve +import Crypto.Noise.Curve.Curve25519 +import Crypto.Noise.Handshake +import Crypto.Noise.HandshakePatterns +import Crypto.Noise.Hash.BLAKE2s +import Crypto.Noise.Types + +import Data.Time.TAI64 + +w :: PublicKey Curve25519 + -> Plaintext + -> Socket + -> SockAddr + -> ByteString + -> IO () +w theirPub (Plaintext myPSK) sock addr msg = do + let x = "\x01\x00\x00" `mappend` msg + mac = hash 16 myPSK (sbToBS' (curvePubToBytes theirPub) `mappend` sbToBS' x) + void $ NBS.sendTo sock (x `mappend` mac `mappend` replicate 16 '\0') addr + +r :: MVar ByteString -> Socket -> IO ByteString +r smv sock = do + (r, _) <- NBS.recvFrom sock 1024 + putMVar smv $ (take 2 . drop 1) r + return . take 48 . drop 5 $ r + +payload :: IO Plaintext +payload = do + tai64n <- getCurrentTAI64N + return . Plaintext . bsToSB' $ S.encode tai64n + +main :: IO () +main = do + let ip = "test.wireguard.io" + let port = "51820" + let mykey = "WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=" + let serverkey = "qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=" + let psk = "FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=" + addrInfo <- head <$> getAddrInfo Nothing (Just ip) (Just port) + sock <- socket (addrFamily addrInfo) Datagram defaultProtocol + + let addr = addrAddress addrInfo + mykey' = curveBytesToPair . bsToSB' . either undefined id . B64.decode . pack $ mykey :: KeyPair Curve25519 + serverkey' = curveBytesToPub . bsToSB' . either undefined id . B64.decode . pack $ serverkey :: PublicKey Curve25519 + psk' = Plaintext . bsToSB' . either undefined id . B64.decode . pack $ psk + hs = handshakeState $ HandshakeStateParams + noiseIK + "WireGuard v0 zx2c4 Jason@zx2c4.com" + (Just psk') + (Just mykey') + Nothing + (Just serverkey') + Nothing + True :: HandshakeState ChaChaPoly1305 Curve25519 BLAKE2s + + senderindexmv <- newEmptyMVar + let hc = HandshakeCallbacks (w serverkey' psk' sock addr) (r senderindexmv sock) (\_ -> return ()) payload + (encryption, decryption) <- runHandshake hs hc + + let (keepAlive, encryption') = encryptPayload "" encryption + senderindex <- takeMVar senderindexmv + void $ NBS.sendTo sock ("\x04" `mappend` senderindex `mappend` replicate 8 '\0' `mappend` keepAlive) addr diff --git a/contrib/external-tests/rust/.gitignore b/contrib/external-tests/rust/.gitignore new file mode 100644 index 0000000..1e7caa9 --- /dev/null +++ b/contrib/external-tests/rust/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ diff --git a/contrib/external-tests/rust/Cargo.toml b/contrib/external-tests/rust/Cargo.toml new file mode 100644 index 0000000..c064905 --- /dev/null +++ b/contrib/external-tests/rust/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "wireguard-keepalive" +version = "0.1.0" +authors = ["jason@zx2c4.com"] +[dependencies] +screech = { git = "https://github.com/trevp/screech" } +rust-crypto = "*" +byteorder = "*" +rustc-serialize = "*" +time = "*" diff --git a/contrib/external-tests/rust/src/main.rs b/contrib/external-tests/rust/src/main.rs new file mode 100644 index 0000000..fa468af --- /dev/null +++ b/contrib/external-tests/rust/src/main.rs @@ -0,0 +1,74 @@ +/* Copyright 2015-2016 Jason A. Donenfeld . All Rights Reserved. */ +extern crate screech; +extern crate crypto; +extern crate time; +extern crate rustc_serialize; +extern crate byteorder; + +use screech::*; +use byteorder::{ByteOrder, BigEndian, LittleEndian}; +use crypto::curve25519::curve25519_base; +use crypto::blake2s::Blake2s; +use rustc_serialize::base64::FromBase64; +use std::net::*; + +fn memcpy(out: &mut [u8], data: &[u8]) { + for count in 0..data.len() { + out[count] = data[count]; + } +} + +fn main() { + let send_addr = "test.wireguard.io:51820".to_socket_addrs().unwrap().next().unwrap(); + let listen_addr = "0.0.0.0:0".to_socket_addrs().unwrap().next().unwrap(); + let socket = UdpSocket::bind(listen_addr).unwrap(); + let mut empty_payload = [0; 0]; + + let mut their_public = [0; 32]; + memcpy(&mut their_public, &"qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=".from_base64().unwrap()); + let mut my_private = [0; 32]; + memcpy(&mut my_private, &"WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=".from_base64().unwrap()); + let mut my_preshared = [0; 32]; + memcpy(&mut my_preshared, &"FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE=".from_base64().unwrap()); + let my_public = curve25519_base(&my_private); + let mut my_keypair : Dh25519 = Default::default(); + my_keypair.set(&my_private, &my_public); + let mut owner : HandshakeCryptoOwner = Default::default(); + owner.set_s(my_keypair); + owner.set_rs(&their_public); + let mut cipherstate1 : CipherState = Default::default(); + let mut cipherstate2 : CipherState = Default::default(); + let mut handshake = HandshakeState::new_from_owner(&mut owner, true, HandshakePattern::IK, "WireGuard v0 zx2c4 Jason@zx2c4.com".as_bytes(), Some(&my_preshared[..]), &mut cipherstate1, &mut cipherstate2); + + let now = time::get_time(); + let mut tai64n = [0; 12]; + BigEndian::write_i64(&mut tai64n[0..], now.sec); + BigEndian::write_i32(&mut tai64n[8..], now.nsec); + let mut initiation_packet = [0; 145]; + initiation_packet[0] = 1; /* Type: Initiation */ + LittleEndian::write_u32(&mut initiation_packet[1..], 28); /* Sender index: 28 (arbitrary) */ + handshake.write_message(&tai64n, &mut initiation_packet[5..]); + let mut mac_material = [0; 143]; + memcpy(&mut mac_material, &their_public); + memcpy(&mut mac_material[32..], &initiation_packet[0..113]); + let mut mac = [0; 16]; + Blake2s::blake2s(&mut mac, &mac_material, &my_preshared); + memcpy(&mut initiation_packet[113..], &mac); + socket.send_to(&initiation_packet, &send_addr).unwrap(); + + let mut response_packet = [0; 89]; + socket.recv_from(&mut response_packet).unwrap(); + assert!(response_packet[0] == 2 /* Type: Response */); + let their_index = LittleEndian::read_u32(&response_packet[1..]); + let our_index = LittleEndian::read_u32(&response_packet[5..]); + assert!(our_index == 28); + let (payload_len, last) = handshake.read_message(&response_packet[9..57], &mut empty_payload).unwrap(); + assert!(payload_len == 0 && last); + + let mut keepalive_packet = [0; 29]; + keepalive_packet[0] = 4; /* Type: Data */ + LittleEndian::write_u32(&mut keepalive_packet[1..], their_index); + LittleEndian::write_u64(&mut keepalive_packet[5..], cipherstate1.n); + cipherstate1.encrypt(&empty_payload, &mut keepalive_packet[13..]); /* Empty payload means keepalive */ + socket.send_to(&keepalive_packet, &send_addr).unwrap(); +} -- cgit v1.2.3-59-g8ed1b