summaryrefslogtreecommitdiff
path: root/src/Pairing.hs
diff options
context:
space:
mode:
authorRoman Smrž <roman.smrz@seznam.cz>2020-08-08 20:11:59 +0200
committerRoman Smrž <roman.smrz@seznam.cz>2020-08-09 21:44:00 +0200
commit3caea2bfbbddf2b3d872eb16e15877194bdfe0d6 (patch)
tree418cb1398c6254679b0d8be74e95b35c330365da /src/Pairing.hs
parenta4437f0479a721aeebac305e403b88b18a5f7d5f (diff)
Move pairing logic to separate module
Diffstat (limited to 'src/Pairing.hs')
-rw-r--r--src/Pairing.hs163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/Pairing.hs b/src/Pairing.hs
new file mode 100644
index 0000000..a0a19b3
--- /dev/null
+++ b/src/Pairing.hs
@@ -0,0 +1,163 @@
+module Pairing (
+ PairingService(..),
+ PairingState(..),
+ PairingResult(..),
+
+ pairingRequest,
+) where
+
+import Control.Monad.Except
+import Control.Monad.Reader
+
+import Crypto.Random
+
+import Data.Bits
+import Data.ByteArray (Bytes, convert)
+import qualified Data.ByteArray as BA
+import qualified Data.ByteString.Char8 as BC
+import Data.Maybe
+import qualified Data.Text as T
+import Data.Typeable
+import Data.Word
+
+import Identity
+import Network
+import Service
+import State
+import Storage
+
+data PairingService a = PairingRequest RefDigest
+ | PairingResponse Bytes
+ | PairingRequestNonce Bytes
+ | PairingAccept a
+ | PairingDecline
+
+data PairingState a = NoPairing
+ | OurRequest Bytes
+ | OurRequestConfirm (Maybe a)
+ | OurRequestReady
+ | PeerRequest Bytes RefDigest
+ | PeerRequestConfirm
+ | PairingDone
+ | PairingFailed
+
+class (Typeable a, Storable a) => PairingResult a where
+ pairingServiceID :: proxy a -> ServiceID
+ pairingHookRequest :: ServiceHandler (PairingService a) ()
+ pairingHookResponse :: String -> ServiceHandler (PairingService a) ()
+ pairingHookRequestNonce :: String -> ServiceHandler (PairingService a) ()
+ pairingHookRequestNonceFailed :: ServiceHandler (PairingService a) ()
+ pairingHookConfirm :: a -> ServiceHandler (PairingService a) (Maybe a)
+ pairingHookAccept :: a -> ServiceHandler (PairingService a) ()
+
+
+instance Storable a => Storable (PairingService a) where
+ store' (PairingRequest x) = storeRec $ storeBinary "request" x
+ store' (PairingResponse x) = storeRec $ storeBinary "response" x
+ store' (PairingRequestNonce x) = storeRec $ storeBinary "reqnonce" x
+ store' (PairingAccept x) = store' x
+ store' (PairingDecline) = storeRec $ storeText "decline" ""
+
+ load' = do
+ res <- loadRec $ do
+ (req :: Maybe Bytes) <- loadMbBinary "request"
+ rsp <- loadMbBinary "response"
+ rnonce <- loadMbBinary "reqnonce"
+ (decline :: Maybe T.Text) <- loadMbText "decline"
+ return $ catMaybes
+ [ PairingRequest <$> (refDigestFromByteString =<< req)
+ , PairingResponse <$> rsp
+ , PairingRequestNonce <$> rnonce
+ , const PairingDecline <$> decline
+ ]
+ case res of
+ x:_ -> return x
+ [] -> PairingAccept <$> load'
+
+
+instance PairingResult a => Service (PairingService a) where
+ serviceID _ = pairingServiceID @a Proxy
+
+ type ServiceState (PairingService a) = PairingState a
+ emptyServiceState _ = NoPairing
+
+ serviceHandler spacket = ((,fromStored spacket) <$> svcGet) >>= \case
+ (NoPairing, PairingRequest confirm) -> do
+ pairingHookRequest
+ nonce <- liftIO $ getRandomBytes 32
+ svcSet $ PeerRequest nonce confirm
+ replyPacket $ PairingResponse nonce
+ (NoPairing, _) -> return ()
+
+ (OurRequest nonce, PairingResponse pnonce) -> do
+ peer <- asks $ svcPeer
+ self <- maybe (throwError "failed to validate own identity") return .
+ validateIdentity . lsIdentity . fromStored =<< svcGetLocal
+ pairingHookResponse $ confirmationNumber $ nonceDigest self peer nonce pnonce
+ svcSet $ OurRequestConfirm Nothing
+ replyPacket $ PairingRequestNonce nonce
+ (OurRequest _, _) -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+
+ (OurRequestConfirm _, PairingAccept x) -> do
+ (svcSet . OurRequestConfirm =<< pairingHookConfirm x) `catchError` \_ -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+
+ (OurRequestConfirm _, _) -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+
+ (OurRequestReady, PairingAccept x) -> do
+ pairingHookAccept x `catchError` \_ -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+ (OurRequestReady, _) -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+
+ (PeerRequest nonce dgst, PairingRequestNonce pnonce) -> do
+ peer <- asks $ svcPeer
+ self <- maybe (throwError "failed to verify own identity") return .
+ validateIdentity . lsIdentity . fromStored =<< svcGetLocal
+ if dgst == nonceDigest peer self pnonce BA.empty
+ then do pairingHookRequestNonce $ confirmationNumber $ nonceDigest peer self pnonce nonce
+ svcSet PeerRequestConfirm
+ else do pairingHookRequestNonceFailed
+ svcSet PairingFailed
+ replyPacket PairingDecline
+ (PeerRequest _ _, _) -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+ (PeerRequestConfirm, _) -> do
+ svcSet $ PairingFailed
+ replyPacket PairingDecline
+
+ (PairingDone, _) -> return ()
+ (PairingFailed, _) -> return ()
+
+
+nonceDigest :: UnifiedIdentity -> UnifiedIdentity -> Bytes -> Bytes -> RefDigest
+nonceDigest id1 id2 nonce1 nonce2 = hashToRefDigest $ serializeObject $ Rec
+ [ (BC.pack "id", RecRef $ storedRef $ idData id1)
+ , (BC.pack "id", RecRef $ storedRef $ idData id2)
+ , (BC.pack "nonce", RecBinary $ convert nonce1)
+ , (BC.pack "nonce", RecBinary $ convert nonce2)
+ ]
+
+confirmationNumber :: RefDigest -> String
+confirmationNumber dgst = let (a:b:c:d:_) = map fromIntegral $ BA.unpack dgst :: [Word32]
+ str = show $ (a .|. (b `shift` 8) .|. (c `shift` 16) .|. (d `shift` 24)) `mod` (10 ^ len)
+ in replicate (len - length str) '0' ++ str
+ where len = 6
+
+pairingRequest :: forall a m proxy. (PairingResult a, MonadIO m, MonadError String m) => proxy a -> UnifiedIdentity -> Peer -> m ()
+pairingRequest _ self peer = do
+ nonce <- liftIO $ getRandomBytes 32
+ pid <- case peerIdentity peer of
+ PeerIdentityFull pid -> return pid
+ _ -> throwError "incomplete peer identity"
+ sendToPeerWith @(PairingService a) self peer $ \case
+ NoPairing -> return (Just $ PairingRequest (nonceDigest self pid nonce BA.empty), OurRequest nonce)
+ _ -> throwError "alredy in progress"