haskell: re-add updated haskell example
Code-from: John Galt <jgalt@centromere.net> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
parent
f90f8f33a7
commit
e7fd4cfd3f
5 changed files with 268 additions and 0 deletions
2
contrib/external-tests/haskell/Setup.hs
Normal file
2
contrib/external-tests/haskell/Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
36
contrib/external-tests/haskell/package.yaml
Normal file
36
contrib/external-tests/haskell/package.yaml
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
name: cacophony-wg
|
||||||
|
version: 0.1.0
|
||||||
|
license: PublicDomain
|
||||||
|
maintainer: John Galt <jgalt@centromere.net>
|
||||||
|
category: Cryptography
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
executables:
|
||||||
|
cacophony-wg:
|
||||||
|
main: Main.hs
|
||||||
|
source-dirs: src
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- base
|
||||||
|
- base16-bytestring
|
||||||
|
- base64-bytestring
|
||||||
|
- blake2
|
||||||
|
- bytestring
|
||||||
|
- cacophony >= 0.10
|
||||||
|
- cereal
|
||||||
|
- cryptonite
|
||||||
|
- memory
|
||||||
|
- network
|
||||||
|
- time
|
||||||
|
|
||||||
|
ghc-options:
|
||||||
|
- -O2
|
||||||
|
- -rtsopts
|
||||||
|
- -threaded
|
||||||
|
- -with-rtsopts=-N
|
||||||
|
|
||||||
|
other-modules:
|
||||||
|
- Data.Time.TAI64
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
- OverloadedStrings
|
86
contrib/external-tests/haskell/src/Data/Time/TAI64.hs
Normal file
86
contrib/external-tests/haskell/src/Data/Time/TAI64.hs
Normal file
|
@ -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
|
138
contrib/external-tests/haskell/src/Main.hs
Normal file
138
contrib/external-tests/haskell/src/Main.hs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Control.Monad (void)
|
||||||
|
import Crypto.Hash.BLAKE2.BLAKE2s (hash)
|
||||||
|
import Data.ByteArray (ScrubbedBytes, convert)
|
||||||
|
import Data.ByteString (ByteString, replicate, take, drop)
|
||||||
|
import qualified Data.ByteString.Base16 as B16
|
||||||
|
import qualified Data.ByteString.Base64 as B64
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import qualified Data.Serialize as S
|
||||||
|
import Network.Socket
|
||||||
|
import qualified Network.Socket.ByteString as NBS
|
||||||
|
import Prelude hiding (replicate, take, drop)
|
||||||
|
|
||||||
|
import Crypto.Noise
|
||||||
|
import Crypto.Noise.Cipher
|
||||||
|
import Crypto.Noise.Cipher.ChaChaPoly1305
|
||||||
|
import Crypto.Noise.DH
|
||||||
|
import Crypto.Noise.DH.Curve25519
|
||||||
|
import Crypto.Noise.HandshakePatterns (noiseIKpsk2)
|
||||||
|
import Crypto.Noise.Hash hiding (hash)
|
||||||
|
import Crypto.Noise.Hash.BLAKE2s
|
||||||
|
|
||||||
|
import Data.Time.TAI64
|
||||||
|
|
||||||
|
sampleICMPRequest :: ByteString
|
||||||
|
sampleICMPRequest = fst . B16.decode $
|
||||||
|
"450000250000000014018f5b0abd81020abd810108001bfa039901b6576972654775617264"
|
||||||
|
|
||||||
|
validateICMPResponse :: ByteString
|
||||||
|
-> Bool
|
||||||
|
validateICMPResponse r =
|
||||||
|
-- Strip off part of IPv4 header because this is only a demo.
|
||||||
|
drop 12 sample == drop 12 r
|
||||||
|
where
|
||||||
|
sample = fst . B16.decode $ "45000025e3030000400180570abd81010abd8102000023fa039901b65769726547756172640000000000000000000000"
|
||||||
|
|
||||||
|
unsafeMessage :: (Cipher c, DH d, Hash h)
|
||||||
|
=> Bool
|
||||||
|
-> Maybe ScrubbedBytes
|
||||||
|
-> ScrubbedBytes
|
||||||
|
-> NoiseState c d h
|
||||||
|
-> (ScrubbedBytes, NoiseState c d h)
|
||||||
|
unsafeMessage write mpsk msg ns = case operation msg ns of
|
||||||
|
NoiseResultMessage ct ns' -> (ct, ns')
|
||||||
|
|
||||||
|
NoiseResultNeedPSK ns' -> case mpsk of
|
||||||
|
Nothing -> error "psk required but not provided"
|
||||||
|
Just k -> case operation k ns' of
|
||||||
|
NoiseResultMessage ct ns'' -> (ct, ns'')
|
||||||
|
_ -> error "something terrible happened"
|
||||||
|
|
||||||
|
_ -> error "something terrible happened"
|
||||||
|
where
|
||||||
|
operation = if write then writeMessage else readMessage
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
let ip = "demo.wireguard.io"
|
||||||
|
port = "12913"
|
||||||
|
myKeyB64 = "WAmgVYXkbT2bCtdcDwolI88/iVi/aV3/PHcUBTQSYmo=" -- private key
|
||||||
|
serverKeyB64 = "qRCwZSKInrMAq5sepfCdaCsRJaoLe5jhtzfiw7CjbwM=" -- public key
|
||||||
|
pskB64 = "FpCyhws9cxwWoV4xELtfJvjJN+zQVRPISllRWgeopVE="
|
||||||
|
|
||||||
|
addrInfo <- head <$> getAddrInfo Nothing (Just ip) (Just port)
|
||||||
|
sock <- socket (addrFamily addrInfo) Datagram defaultProtocol
|
||||||
|
|
||||||
|
let addr = addrAddress addrInfo
|
||||||
|
myStaticKey = fromMaybe (error "invalid private key")
|
||||||
|
. dhBytesToPair
|
||||||
|
. convert
|
||||||
|
. either (error "error Base64 decoding my private key") id
|
||||||
|
. B64.decode
|
||||||
|
$ myKeyB64 :: KeyPair Curve25519
|
||||||
|
|
||||||
|
serverKey = fromMaybe (error "invalid public key")
|
||||||
|
. dhBytesToPub
|
||||||
|
. convert
|
||||||
|
. either (error "error Base64 decoding server public key") id
|
||||||
|
. B64.decode
|
||||||
|
$ serverKeyB64 :: PublicKey Curve25519
|
||||||
|
|
||||||
|
psk = convert
|
||||||
|
. either (error "error decoding PSK") id
|
||||||
|
. B64.decode
|
||||||
|
$ pskB64 :: ScrubbedBytes
|
||||||
|
|
||||||
|
myEphemeralKey <- dhGenKey
|
||||||
|
|
||||||
|
let dho = defaultHandshakeOpts InitiatorRole "WireGuard v1 zx2c4 Jason@zx2c4.com"
|
||||||
|
opts = setLocalEphemeral (Just myEphemeralKey)
|
||||||
|
. setLocalStatic (Just myStaticKey)
|
||||||
|
. setRemoteStatic (Just serverKey)
|
||||||
|
$ dho
|
||||||
|
ns0 = noiseState opts noiseIKpsk2 :: NoiseState ChaChaPoly1305 Curve25519 BLAKE2s
|
||||||
|
|
||||||
|
tai64n <- convert . S.encode <$> getCurrentTAI64N
|
||||||
|
|
||||||
|
-- Handshake: Initiator to responder -----------------------------------------
|
||||||
|
|
||||||
|
let (msg0, ns1) = unsafeMessage True Nothing tai64n ns0
|
||||||
|
macKey = hash 32 mempty $ "mac1----" `mappend` (convert . dhPubToBytes) serverKey
|
||||||
|
initiation = "\x01\x00\x00\x00\x1c\x00\x00\x00" <> convert msg0 -- sender index = 28 to match other examples
|
||||||
|
mac1 = hash 16 macKey initiation
|
||||||
|
|
||||||
|
void $ NBS.sendTo sock (initiation <> mac1 <> replicate 16 0) addr
|
||||||
|
|
||||||
|
-- Handshake: Responder to initiator -----------------------------------------
|
||||||
|
|
||||||
|
(response0, _) <- NBS.recvFrom sock 1024
|
||||||
|
|
||||||
|
let theirIndex = take 4 . drop 4 $ response0
|
||||||
|
(_, ns2) = unsafeMessage False (Just psk) (convert . take 48 . drop 12 $ response0) ns1
|
||||||
|
|
||||||
|
-- ICMP: Initiator to responder ----------------------------------------------
|
||||||
|
|
||||||
|
let (msg1, ns3) = unsafeMessage True Nothing (convert sampleICMPRequest) ns2
|
||||||
|
icmp = "\x04\x00\x00\x00" <> theirIndex <> replicate 8 0 <> convert msg1
|
||||||
|
|
||||||
|
void $ NBS.sendTo sock icmp addr
|
||||||
|
|
||||||
|
-- ICMP: Responder to initiator ----------------------------------------------
|
||||||
|
|
||||||
|
(response1, _) <- NBS.recvFrom sock 1024
|
||||||
|
|
||||||
|
let (icmpPayload, ns4) = unsafeMessage False Nothing (convert . drop 16 $ response1) ns3
|
||||||
|
|
||||||
|
-- KeepAlive: Initiator to responder -----------------------------------------
|
||||||
|
|
||||||
|
if validateICMPResponse . convert $ icmpPayload
|
||||||
|
then do
|
||||||
|
let (msg2, _) = unsafeMessage True Nothing mempty ns4
|
||||||
|
keepAlive = "\x04\x00\x00\x00" <> theirIndex <> "\x01" <> replicate 7 0 <> convert msg2
|
||||||
|
|
||||||
|
void $ NBS.sendTo sock keepAlive addr
|
||||||
|
|
||||||
|
else error "unexpected ICMP response from server!"
|
6
contrib/external-tests/haskell/stack.yaml
Normal file
6
contrib/external-tests/haskell/stack.yaml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
resolver: lts-8.18
|
||||||
|
packages:
|
||||||
|
- '.'
|
||||||
|
extra-deps: []
|
||||||
|
flags: {}
|
||||||
|
extra-package-dbs: []
|
Loading…
Reference in a new issue