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
					
				
							
								
								
									
										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: []
 | 
				
			||||||
		Reference in New Issue
	
	Block a user