new protocol encryption + minor tuning

This commit is contained in:
Dmitry Zuikov 2023-10-25 09:01:13 +03:00
parent 9edbe5f7c9
commit c44a6b997b
25 changed files with 1647 additions and 380 deletions

View File

@ -1,3 +1,7 @@
## 2023-10-22
тестируем шифрование на уровне протокола
## 2023-10-21
тестируем substituter

349
docs/proto/encryption.texi Normal file
View File

@ -0,0 +1,349 @@
\input texinfo
@documentencoding UTF-8
@node Top
@top Дизайн шифрования протокола
@chapter Предпосылки
@section Борьба с DPI
С высокой вероятностью, на текущем нашем уровне понимания
проблемы, DPI будут распознавать протокол всё равно.
Соответственно, нет большой разницы, один - два или ноль
незашифрованных пакетов пройдёт.
Само наличие шифрования --- уже признак для DPI, могут
блокировать просто на основании принадлежности неизвестному
протоколу.
Размеры пакетов и характер обмена --- тоже признаки.
Saltine, возможно, оставляет сигнатуры в зашифрованных пакетах
(проверить).
Таким образом, борьба с DPI совершенно точно должна
осуществляться отдельным Messaging, может быть даже внешним.
Нашего текущего уровня понимания проблемы просто-таки
недостаточно для эффективной борьбы без радикального усложнения
себе жизни.
В дальнейшем, для протокола борьбы с DPI можно выделить даже
отдельные порты, где будут ожидаться другие протоколы.
Скорее всего, нужно будет просто написать транспорт поверх
WS/WSS, а внутренние датаграммы шифровать как обычно.
Таким образом, наверху будет TLS, а внизу --- наше шифрование
пакетов, при этом протокол будет принадлежать к "хорошо
известным протоколам". Можно даже палёные сертификаты
использовать, что бы успокоить службы (на верхнем уровне).
@section Различение шифрованных и нешифрованных пакетов
Возможны следующие способы:
@itemize @bullet
@item Номера портов
@item Сам протокол
@end itemize
@subsection Номера портов
Запускаем Messaging на отдельном порту, там воркер,
который занимается шифрованием и хэндшейком,
по мере расшифровки --- передает данные в указанный Messaging
(Proxy).
Плюсы:
@itemize @bullet
@item Не нужно заглядывать в пакеты: будет быстрее.
@item Не нужны префиксы в протоколе
@item Не будет интерференции с остальными протоколами
@item Обратная совместимость
@end itemize
Минусы:
@itemize @bullet
@item Доработка PEX
@item Доработка бутстрапа
@item Различение шифрованных и нешифрованных протоколов либо
хардкода портов
@item Устойчивые порты => легко блокировать
@end itemize
@subsection Сам протокол
Поскольку Messaing работает @strong{ДО}, мы можем в нём
распаковывать пакет, доставать сообщение и передавать в Peer уже
расшифрованное.
У нас есть два варианта:
Сообщение зашифрованное и незашифрованное.
Допустим, сообщение зашифрованное --- тогда это просто пакет в
формате, который создает libsodium.
Незашифрованное --- содержит префикс, например 0xdeadf1od и
дальше --- уже сам пакет.
При приеме сообщения мы отправляем этот префикс в начале пакета.
Если префикс есть --- то смотрим, что это за пакет.
Допустим, это хэндшейк. Тогда производим согласование ключей, и
дальше шлём зашифрованные пакеты.
Допустим, префикс будет только у пакетов хэндшейка, тогда
мы даже сохраним обратную совместимость --- неразобранные пакеты
будем отправлять по стеку дальше.
Минус устойчивой сигнатуры в определенном месте --- легко
блокировать простейшим фильтром.
Несмотря на то, что наши возможности бороться с DPI ограничены
слабым погружением в вопрос, настолько облегчать работу им мы не
хотим.
Можно в префикс встроить некий байткод, выполнение которого
приведёт к вычислению публичного ключа пира, которым можно,
в частности, идентифицировать самого пира.
Тогда это не будет создавать значительных проблем самому
пиру, но будет создавать проблемы при массовом анализе
протоколов. Можно еще и загадку встроить.
Тогда сам пакет будет выглядеть более или менее как мусор,
для анализа придётся построить интерпретатор, а так же встроить
интерпретацию в DPI, успехов им в борьбе.
Можно так же использовать какой-то вид вывода ключей из хорошо
известной, но динамически меняющейся информации.
А можно использовать всё вместе, расширяя этот байткод по мере
необходимости.
Всё это приведёт к большому усложнению и сторонних реализаций
протокола тоже, так что, для начала --- можно выбрать самый
простой способ распознать пакет хэндшейка.
Но пожалуй, я настою на том, что бы это был какой-то
динамический способ, что бы не зависеть от статических
сигнатур.
Вычисляться должно быстро, желательно
в один проход вперёд.
Плюсы:
@itemize @bullet
@item Меньше кода
@item Не меняется PEX
@item Нет устойчивых номеров портов
@item Можно навесить сверху любого протокола,
например, RPC
@end itemize
Минусы:
@itemize @bullet
@item Потенциально медленнее
@end itemize
@subsection Дизайн
Отдельный Proxy на каждый Messaging, с общим KeyStore.
@itemize @bullet
@item Пир сразу идентифицируется для всех протоколов
@item Можно надстроить над любым Messaing, в частности, над RPC,
который у нас пока без средств шифрования и аутентификации.
@end itemize
@verbatim
keys <- newKeyStore
proxy1 <- newProxyEnc keys (newMessaingUDP ...)
proxy2 <- newProxyEnc keys (newMessaingTCP ...)
proxy3 <- newProxyMessaging proxy1 (Just proxy2)
peer <- newPeer ... (Fabriq proxy3)
@end verbatim
Далее. Предусмотрим два режима: bypass и drop.
@subsection Режим bypass
Пробует провести handshake, если не удаётся ---
то оставляет эти попытки и просто пересылвает сообщения,
как есть на следующий уровень.
Хэндшейк делает сам. <<Чужие>> пакеты при этом пропускает
и наверх, и вниз.
Таким образом, остаётся обратная совместимость --- ведь
пакеты этой прокси просто будут дропнуты.
Так же можно пакеты слать культурно, в виде AnyMessage
с каким-то несуществующим типом протокола, который
отсутствует в каких-либо обработчиках --- тогда их
будет видно в логах. Возможно, это излишнее.
@subsection Режим drop
Пробует провести handshake, если не удаётся ---
то не делает ничего, либо повторяет их заданное число раз,
а потом не делает ничего.
Этот режим выбирают пиры, которые настаивают на защищенном
обмене.
@subsection Сообщения и FSM
Незашифрованное:
1. HEY(PREFIX,PKs,PKe,SIGN(PKs, PKe))
@table @asis
@item PKs:
ключ подписи пира
@item PKe:
публичный ключ шифрования сессии
@end table
Зашифрованное:
2. HEYOURSELF(PKe,BOX(PKe,Sid,SECRET))
@table @asis
@item PKe:
публичный ключ шифрования сессии
@item Sid:
Идентификатор ключа на нашей стороне,
должен быть в открытом виде в зашифрованном пакете
@end table
Пересылаем ключ шифрования, теперь пир может слать нам
зашифрованные сообщения.
Просто пытаемся расшифровать их своим секретом. Неудача ---
просто дропаем или пропускаем, в зависимости от политики.
Теперь, если пир продолжает слать незашифрованные
сообщения --- можем их или дропать, или продолжать
пропускать.
После получения HEYOURSELF мы должны пиру послать
наш секрет ключ тоже, так что должны видимо, в свою
очередь ответить HEY.
@verbatim
Peer1 Peer2
***** ****
| HEY |
|------------------>|
| HEYOURSELF |
|<------------------| Теперь Peer1 может слать Peer2
| | зашифрованные сообщения
| |
| HEY |
|<------------------| Теперь Peer2 может слать Peer1
| HEYOURSELF | зашифрованные сообщения
|------------------>|
| |
| |
@end verbatim
Заметим, что это хорошо ложится на систему обработки
подпротоколов в Peer: пары HEY/YOUSELF являются
независимыми, и, кажется, stateless, в том плане,
что нонсом тут явлется сам PKe.
Но! Если это вынести на уровень Peer, то потеряется
возможность навесить аутентификацию/шифрование на любой
Messaging.
Поэтому делаем на уровне Messaging.
Грубо говоря: получили HEYOURSELF --- обновили ключ.
Исключительная ситуация:
Пир потерял наш SECRET и об этом не знает.
Это может случиться только при рестарте, если мы не сохраняем
ключ.
Но это не может случиться, так как, если у ProxyEnc нет ключа
--- то она посылает HEY.
Замечание: ProxyEnc является пассивной, она ничего не знает про
пиров, PEX и сама не инициирует общение, так как не знает, с кем
общаться.
Поэтому, в режиме Bypass --- просто пропускает сообщения, как
есть, в обе стороны, не препятствуя. Как только удалось
согласовать ключ --- то есть, получить HEYOURSELF --- то
включаем шифрование.
Если пинг прислал нам HEYOURSELF, а сам незашифрованные
сообщения шлёт -- ну дурак, чо. Надо проинформировать об этом
в лог, может забанить его.
В режиме Drop -- ставит сообщения в очередь, пока не появился
ключ.
Как только ключ появился --- шифрует и отправляет.
Пиры авторизуются друг у друга при помощи протокола Ping.
Никаких других ходить не должно до этого, это в принципе ошибка.
Соответственно, пока пиры не авторизовались --- никакого обмена
нет.
Даже если мы этот пинг дропнем --- пир нас пинганёт еще раз.
@strong{Про Sid} Так как один и тот же пир может быть под разными
адресами одновременно, а PeerNonce и прочее --- на этом уровне протокола
недоступно --- будем генерировать уникальный ключ для каждого пира по
адресу, и в HEYOURSELF пересылать Sid ключа. Пир будет сохранять пару
(Sid, SECRET) --- и в ответе будет брать нужный ключ.
Проблема, если мы пишем пиру на один адрес (каким нам ключом шифровать?),
а он отвечает нам с другого адреса.
PeerNonce недоступен, что делать?
Кейс:
Peer1: Peer2:
tcp:1.1.1.1:8957 -> tcp:1.1.10.5:8001
<- tcp:1.1.10.5:62511
В HEYO(SELF) посылаем ему ID ключа, он где-то (где?) запоминает,
что нам надо слать c таким ID ключа.
@bye

View File

@ -101,6 +101,8 @@ library
, HBS2.Net.Messaging.TCP
, HBS2.Net.Messaging.Unix
, HBS2.Net.Messaging.Stream
, HBS2.Net.Messaging.Encrypted.RandomPrefix
, HBS2.Net.Messaging.Encrypted.ByPass
, HBS2.Net.PeerLocator
, HBS2.Net.PeerLocator.Static
, HBS2.Net.Proto

View File

@ -47,20 +47,6 @@ import Control.Monad.IO.Unlift
import Codec.Serialise (serialise, deserialiseOrFail)
data AnyMessage enc e = AnyMessage !Integer !(Encoded e)
deriving stock (Generic)
class ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e)
, Eq (Encoded e)
, Hashable (Encoded e)
) => PeerMessaging e
instance ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e)
, Eq (Encoded e)
, Hashable (Encoded e)
)
=> PeerMessaging e
class ( Eq (SessionKey e a)
, Hashable (SessionKey e a)
@ -379,6 +365,7 @@ newPeerEnv :: forall e m . ( MonadIO m
, Ord (Peer e)
, Pretty (Peer e)
, HasNonces () m
, PeerMessaging e
, Asymm (Encryption e)
, Hashable (PubKey 'Sign (Encryption e))
, Hashable PeerNonce
@ -457,7 +444,7 @@ runProto hh = do
case Map.lookup n disp of
Nothing -> do
err $ "PROTO not found" <+> pretty n <+> pretty (fmap fst resp)
-- err $ "PROTO not found" <+> pretty n <+> pretty (fmap fst resp)
pure () -- FIXME: error counting! and statistics counting feature
Just (AnyProtocol { protoDecode = decoder

View File

@ -36,7 +36,15 @@ class Monad m => HasOwnPeer e m where
data Fabriq e = forall bus . (Messaging bus e (Encoded e)) => Fabriq bus
class HasFabriq e m where
getFabriq :: m (Fabriq e)
data AnyMessage enc e = AnyMessage !Integer !(Encoded e)
deriving stock (Generic)
type PeerMessaging e = ( Messaging (Fabriq e) e (AnyMessage (Encoded e) e)
, Eq (Encoded e)
, Hashable (Encoded e)
)

View File

@ -162,9 +162,6 @@ lookupGroupKey sk pk gk = runIdentity $ runMaybeT do
-- error $ "DECRYPTED SHIT!"
MaybeT $ pure $ deserialiseOrFail (LBS.fromStrict gkBs) & either (const Nothing) Just
-- FIXME: move-to-appropriate-place
class NonceFrom nonce a where
nonceFrom :: a -> nonce
typicalNonceLength :: Integral a => a
typicalNonceLength = unsafePerformIO SK.newNonce & Saltine.encode & B8.length & fromIntegral

View File

@ -1,7 +1,9 @@
{-# Language FunctionalDependencies #-}
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
module HBS2.Net.Messaging where
module HBS2.Net.Messaging
( module HBS2.Net.Messaging
, module HBS2.Net.Proto
) where
import HBS2.Net.Proto
@ -20,3 +22,4 @@ class HasPeer proto => Messaging bus proto msg where
sendTo :: MonadIO m => bus -> To proto -> From proto -> msg -> m ()
receive :: MonadIO m => bus -> To proto -> m [(From proto, msg)]

View File

@ -0,0 +1,517 @@
{-# Language UndecidableInstances #-}
{-# Language RecordWildCards #-}
module HBS2.Net.Messaging.Encrypted.ByPass
( ForByPass
, ByPass
, ByPassOpts(..)
, ByPassStat(..)
, byPassDef
, newByPassMessaging
, cleanupByPassMessaging
, getStat
) where
import HBS2.Prelude
import HBS2.Hash
import HBS2.Clock hiding (sec)
import HBS2.Net.Messaging
import HBS2.Data.Types.SignedBox
import HBS2.Net.Proto.Definition()
import HBS2.Net.Auth.Credentials()
import HBS2.Net.Messaging.Encrypted.RandomPrefix
import HBS2.System.Logger.Simple
import Codec.Serialise
import Control.Concurrent.STM (flushTQueue)
import Control.Monad.Identity
import Control.Monad.Trans.Maybe
import Network.ByteOrder qualified as N
import Crypto.Saltine.Core.Box (Keypair(..),CombinedKey)
import Crypto.Saltine.Class qualified as SA
import Crypto.Saltine.Core.Box qualified as PKE
import Data.Bits
import Data.ByteArray.Hash qualified as BA
import Data.ByteArray.Hash (SipHash(..), SipKey(..))
import Data.ByteString.Lazy (ByteString)
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString qualified as BS
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.HashSet qualified as HashSet
import Data.Maybe
import Data.Time.Clock.POSIX
import Data.Word
import System.Random
import System.IO.Unsafe (unsafePerformIO)
import UnliftIO
heySeed :: Word8
heySeed = 117
newtype NonceA = NonceA { fromNonceA :: Word16 }
deriving newtype (Eq,Ord,Show,Pretty,Real,Num,Enum,Integral,Hashable)
deriving stock Generic
type FlowKey = Word32
instance Serialise NonceA
mySipHash :: Integral a => BS.ByteString -> a
mySipHash s = BA.sipHash (SipKey a b) s
& \(SipHash w) -> fromIntegral w
where
a = 3857206264
b = 1307114574
-- NOTE: key-update-on-fly
-- мы можем на ходу менять ключи:
-- меняем nonceA, перегенеряем ключ, больше ничего не трогаем:
-- тогда пакеты посланные для старого nonceA можно будет расшифровать,
-- а шифровать уже для нового.
--
-- Таким образом, хост может иметь много flow с разными
-- нонсами одновременно
--
data ByPassOpts e =
ByPassOpts
{ byPassEnabled :: Bool
, byPassKeyAllowed :: PubKey 'Sign (Encryption e) -> IO Bool
, byPassTimeRange :: Maybe (Int, Int)
}
data ByPassStat =
ByPassStat
{ statBypassed :: Int
, statEncrypted :: Int
, statDecrypted :: Int
, statDecryptFails :: Int
, statSent :: Int
, statReceived :: Int
, statFlowNum :: Int
, statPeers :: Int
, statAuthFail :: Int
}
deriving stock (Show,Generic)
instance Serialise ByPassStat
data ByPass e them =
ByPass
{ opts :: ByPassOpts e
, self :: Peer e
, pks :: PubKey 'Sign (Encryption e)
, sks :: PrivKey 'Sign (Encryption e)
, pke :: PubKey 'Encrypt (Encryption e)
, ske :: PrivKey 'Encrypt (Encryption e)
, proxied :: them
, nonceA :: NonceA
, delayed :: TQueue (To e, ByteString)
, heySent :: TVar (HashMap (Peer e) TimeSpec)
, noncesByPeer :: TVar (HashMap (Peer e) NonceA)
, flowKeys :: TVar (HashMap FlowKey CombinedKey)
, bypassed :: TVar Int
, encrypted :: TVar Int
, decrypted :: TVar Int
, decryptFails :: TVar Int
, sentNum :: TVar Int
, recvNum :: TVar Int
, authFail :: TVar Int
}
type ForByPass e = ( Hashable (Peer e)
, Pretty (Peer e)
, Eq (PubKey 'Sign (Encryption e))
, Serialise (PubKey 'Sign (Encryption e))
, PrivKey 'Encrypt (Encryption e) ~ PKE.SecretKey
, PubKey 'Encrypt (Encryption e) ~ PKE.PublicKey
, ForSignedBox e
)
data HEYBox e =
HEYBox Int (PubKey 'Encrypt (Encryption e))
deriving stock Generic
instance ForByPass e => Serialise (HEYBox e)
data EncryptHandshake e =
HEY
{ heyNonceA :: NonceA
, heyBox :: SignedBox (HEYBox e) e
}
deriving stock (Generic)
instance ForByPass e => Serialise (EncryptHandshake e)
getStat :: forall e w m . ( ForByPass e
, MonadIO m
)
=> ByPass e w
-> m ByPassStat
getStat bus = liftIO $
ByPassStat <$> readTVarIO (bypassed bus)
<*> readTVarIO (encrypted bus)
<*> readTVarIO (decrypted bus)
<*> readTVarIO (decryptFails bus)
<*> readTVarIO (sentNum bus)
<*> readTVarIO (recvNum bus)
<*> (readTVarIO (flowKeys bus) <&> HashMap.size)
<*> (readTVarIO (noncesByPeer bus) <&> HashMap.size)
<*> readTVarIO (authFail bus)
cleanupByPassMessaging :: forall e w m . ( ForByPass e
, MonadIO m
)
=> ByPass e w
-> [Peer e]
-> m ()
cleanupByPassMessaging bus pips = do
debug "cleanupByPassMessaging"
let alive = HashSet.fromList pips
atomically do
sent <- readTVar (heySent bus)
nonces <- readTVar (noncesByPeer bus)
flows <- readTVar (flowKeys bus)
let livePeers = [ (k,v)
| (k,v) <- HashMap.toList nonces
, k `HashSet.member` alive
] & HashMap.fromList
let liveSent = HashMap.filterWithKey (\k _ -> k `HashMap.member` livePeers) sent
let liveFk = [ makeKey (nonceA bus) nonce
| nonce <- HashMap.elems livePeers
] & HashSet.fromList
let liveFlows = HashMap.filterWithKey (\k _ -> k `HashSet.member` liveFk) flows
writeTVar (heySent bus) liveSent
writeTVar (noncesByPeer bus) livePeers
writeTVar (flowKeys bus) liveFlows
byPassDef :: ByPassOpts e
byPassDef =
ByPassOpts
{ byPassEnabled = True
, byPassKeyAllowed = const $ pure True
, byPassTimeRange = Nothing
}
newByPassMessaging :: forall e w m . ( ForByPass e
, MonadIO m
, Messaging w e ByteString
)
=> ByPassOpts e
-> w
-> Peer e
-> PubKey 'Sign (Encryption e)
-> PrivKey 'Sign (Encryption e)
-> m (ByPass e w)
newByPassMessaging o w self ps sk = do
(Keypair s p) <- liftIO PKE.newKeypair
let n = mySipHash (LBS.toStrict (serialise s))
ByPass @e o self ps sk p s w n <$> newTQueueIO
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO 0
instance (ForByPass e, Messaging w e ByteString)
=> Messaging (ByPass e w) e ByteString where
sendTo bus t@(To whom) f m = do
mkey <- lookupEncKey bus whom
atomically $ modifyTVar (sentNum bus) succ
case mkey of
Just fck -> do
sendTo (proxied bus) t f =<< encryptMessage bus fck m
Nothing -> do
-- let ByPassOpts{..} = opts bus
if False then do
atomically $ writeTQueue (delayed bus) (t,m)
else do
trace $ "bypassed message to" <+> pretty whom
atomically $ modifyTVar (bypassed bus) succ
sendTo (proxied bus) t f m
-- TODO: fix-timeout-hardcode
withHeySent bus 30 whom do
sendHey bus whom
receive bus f = do
msgs <- receive (proxied bus) f
q <- newTQueueIO
-- TODO: run-concurrently
for_ msgs $ \(From who, mess) -> runMaybeT do
atomically $ modifyTVar (recvNum bus) succ
hshake <- processHey who mess
guard (not hshake)
msg <- tryDecryptMessage bus mess
case msg of
Just demsg -> do
atomically $ writeTQueue q (From who, demsg)
Nothing -> do
withHeySent bus 60 who do
sendHey bus who
atomically $ writeTQueue q (From who, mess)
liftIO $ atomically $ flushTQueue q
where
processHey orig bs = isJust <$> runMaybeT do
let o = opts bus
let (code, hbs) = runCodeLazy bs
-- FIXME: check-code
guard ( code == Just heySeed )
debug $ "HEY CODE:" <+> pretty code
guard (not (LBS.null hbs))
hshake <- toMPlus (deserialiseOrFail @(EncryptHandshake e) hbs)
case hshake of
HEY{..} -> do-- void $ runMaybeT do
debug $ "GOT HEY MESSAGE" <+> parens (pretty code) <+> pretty heyNonceA
-- FIXME: check-if-key-authorized
let mbx = unboxSignedBox0 heyBox
when (isNothing mbx) do
debug $ "HEY: failed to unbox" <+> pretty heyNonceA <+> pretty orig
n <- toMPlus mbx
-- FIXME: authorize-pk-right-here
(pks, HEYBox t puk) <- toMPlus mbx
let dt = byPassTimeRange o
allowed <- liftIO $ byPassKeyAllowed o pks
now <- liftIO getPOSIXTime <&> round
let actual = maybe1 dt True (\(ta, tb) -> t >= now - ta && t <= now + tb)
let authorized = allowed && actual
unless authorized do
atomically $ modifyTVar (authFail bus) succ
warn $ "ByPass:" <+> "NOT AUTHORIZED" <+> pretty orig
when authorized do
debug $ "ByPass:" <+> "AUTHORIZED" <+> pretty now <+> pretty orig
guard authorized
let fk = makeKey (nonceA bus) heyNonceA
here <- readTVarIO (flowKeys bus) <&> HashMap.member fk
updatePeerNonce bus orig heyNonceA
unless here do
let ck = PKE.beforeNM (ske bus) puk
debug $ "HEY: CK" <+> pretty (nonceA bus)
<+> pretty fk
<+> pretty (hashObject @HbSync (SA.encode ck))
atomically $ do
modifyTVar (flowKeys bus) (HashMap.insert fk ck)
withHeySent bus 30 orig do
sendHey bus orig
pure hshake
makeKey :: NonceA -> NonceA -> FlowKey
makeKey a b = runIdentity do
let aa = fromIntegral a :: FlowKey
let bb = fromIntegral b :: FlowKey
let (f0,f1) = if aa < bb then (aa,bb) else (bb,aa)
pure $ (f0 `shiftL` 16) .|. f1
sendHey :: forall e w m . ( ForByPass e
, Messaging w e ByteString
, MonadIO m
)
=> ByPass e w
-> Peer e
-> m ()
sendHey bus whom = do
pref <- randomPrefix (PrefixMethod1 4 11 heySeed) <&> toLazyByteString
let (code, _) = runCodeLazy pref
ts <- liftIO getPOSIXTime <&> round
let hbox = HEYBox @e ts (pke bus)
let box = makeSignedBox @e (pks bus) (sks bus) hbox
let hey = HEY @e (nonceA bus) box
let msg = pref <> serialise hey
debug $ "SEND HEY" <+> pretty (heyNonceA hey)
<+> parens ("seed" <+> pretty code)
<+> pretty whom
<+> pretty (LBS.length msg)
sendTo (proxied bus) (To whom) (From (self bus)) msg
withHeySent :: forall e w m . (MonadIO m, ForByPass e)
=> ByPass e w
-> Timeout 'Seconds
-> Peer e
-> m ()
-> m ()
withHeySent w ts pip m = do
now <- getTimeCoarse
t0 <- readTVarIO (heySent w) <&> HashMap.lookup pip
<&> fromMaybe 0
let elapsed = toNanoSeconds $ TimeoutTS (now - t0)
when ( elapsed >= toNanoSeconds ts ) do
atomically $ modifyTVar (heySent w) (HashMap.insert pip now)
m
updatePeerNonce :: forall e w m . ( ForByPass e
, MonadIO m
)
=> ByPass e w
-> Peer e
-> NonceA
-> m ()
updatePeerNonce bus pip nonce = do
atomically $ modifyTVar (noncesByPeer bus) (HashMap.insert pip nonce)
lookupEncKey :: (ForByPass e, MonadIO m) => ByPass e w -> Peer e -> m (Maybe (FlowKey, CombinedKey))
lookupEncKey bus whom = runMaybeT do
nonce <- MaybeT $ readTVarIO (noncesByPeer bus) <&> HashMap.lookup whom
let fk = makeKey nonce (nonceA bus)
ck <- MaybeT $ readTVarIO (flowKeys bus) <&> HashMap.lookup fk
pure (fk, ck)
typicalNonceLength :: Integral a => a
typicalNonceLength = unsafePerformIO PKE.newNonce & SA.encode & BS.length & fromIntegral
{-# NOINLINE typicalNonceLength #-}
newtype ByPassNonce = ByPassNonce { unByPassNonce :: PKE.Nonce }
instance NonceFrom ByPassNonce Word32 where
nonceFrom a = ByPassNonce nonce
where
n = typicalNonceLength
nonce = fromJust (SA.decode s)
s = BS.take n (N.bytestring32 a <> BS.replicate n 0)
tryDecryptMessage :: (MonadIO m, ForByPass e)
=> ByPass e w
-> ByteString
-> m (Maybe ByteString)
tryDecryptMessage bus bs = runMaybeT do
let (hdr, body) = LBS.splitAt 8 bs
guard (LBS.length hdr == 8)
(fk, wnonce) <- liftIO $ N.withReadBuffer (LBS.toStrict hdr) $ \buf -> do
(,) <$> N.read32 buf <*> N.read32 buf
let bnonce = nonceFrom @ByPassNonce wnonce
ck <- MaybeT $ readTVarIO (flowKeys bus) <&> HashMap.lookup fk
let dmess = PKE.boxOpenAfterNM ck (unByPassNonce bnonce) (LBS.toStrict body) <&> LBS.fromStrict
atomically do
maybe1 dmess
(modifyTVar (decryptFails bus) succ)
(const $ modifyTVar (decrypted bus) succ)
toMPlus dmess
encryptMessage :: (MonadIO m, ForByPass e)
=> ByPass e w
-> (FlowKey, CombinedKey)
-> ByteString
-> m ByteString
encryptMessage bus (fk, ck) bs = do
atomically $ modifyTVar (encrypted bus) succ
wnonce <- liftIO (randomIO @Word32)
let bnonce = nonceFrom @ByPassNonce wnonce
let ebs = PKE.boxAfterNM ck (unByPassNonce bnonce) (LBS.toStrict bs)
let pkt = mconcat [ word32BE fk
, word32BE wnonce
, byteString ebs
] & toLazyByteString
pure pkt
instance Pretty ByPassStat where
pretty (ByPassStat{..}) =
vcat [ prettyField "bypassed" statBypassed
, prettyField "encrypted" statEncrypted
, prettyField "decrypted" statDecrypted
, prettyField "decryptFails" statDecryptFails
, prettyField "sent" statSent
, prettyField "received" statReceived
, prettyField "flowNum" statFlowNum
, prettyField "peers" statPeers
, prettyField "authFail" statAuthFail
]
where
prettyField x e = fill 15 (x <> colon) <+> pretty e

View File

@ -0,0 +1,244 @@
{-# Language AllowAmbiguousTypes #-}
{-# Language UndecidableInstances #-}
{-# Language TypeFamilyDependencies #-}
module HBS2.Net.Messaging.Encrypted.RandomPrefix
( module Data.ByteString.Builder
, runCodeLazy
, RandomPrefix(..)
, PrefixMethod1(..)
) where
import Data.Word
import Data.Bits
-- import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy (ByteString)
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Data.ByteString.Builder
import Data.Maybe
import Lens.Micro.Platform
import Data.Kind
import GHC.TypeLits
import Data.Proxy
import Safe
import Data.List.Split (chunksOf)
import System.Random hiding (next)
data NOP
data LOADB
data SKIPBI
data ANDBI
data ORBI
data XORBI
data ADDBI
data SUBBI
data MULTBI
data REPEAT
data RET
class Emittable a where
emit :: a -> Builder
class (Emittable (Arg a), KnownNat (Opcode a)) => Instruction a where
type family Opcode a = (code :: Nat) | code -> a
type family Arg a :: Type
data OP = forall a . (Instruction a, Emittable (Proxy a)) =>
OP (Proxy a) (Arg a) | BYTE Word8
instance Instruction a => Emittable (Proxy a) where
emit _ = word8 . fromIntegral $ natVal (Proxy @(Opcode a))
instance Emittable OP where
emit (OP op arg) = emit op <> emit arg
emit (BYTE w) = word8 w
instance Emittable () where
emit = mempty
instance Emittable Word8 where
emit = word8
instance Emittable b => Emittable [b] where
emit xs= mconcat (fmap emit xs)
instance Instruction NOP where
type instance Opcode NOP = 0xFE
type instance Arg NOP = ()
instance Instruction LOADB where
type instance Opcode LOADB = 0x01
type instance Arg LOADB = Word8
instance Instruction SKIPBI where
type instance Opcode SKIPBI = 0x02
type instance Arg SKIPBI = Word8
instance Instruction ORBI where
type instance Opcode ORBI= 0x03
type instance Arg ORBI = Word8
instance Instruction ANDBI where
type instance Opcode ANDBI= 0x04
type instance Arg ANDBI = Word8
instance Instruction XORBI where
type instance Opcode XORBI= 0x05
type instance Arg XORBI = Word8
instance Instruction ADDBI where
type instance Opcode ADDBI = 0x06
type instance Arg ADDBI = Word8
instance Instruction SUBBI where
type instance Opcode SUBBI = 0x07
type instance Arg SUBBI = Word8
instance Instruction MULTBI where
type instance Opcode MULTBI = 0x08
type instance Arg MULTBI = Word8
instance Instruction REPEAT where
type instance Opcode REPEAT = 0xC0
type instance Arg REPEAT = Word8
instance Instruction RET where
type instance Opcode RET = 0x00
type instance Arg RET = ()
op :: forall a . Instruction a
=> Arg a
-> OP
op = OP (Proxy @a)
byte :: Word8 -> OP
byte = BYTE
runCodeLazy :: ByteString -> (Maybe Word8, ByteString)
runCodeLazy s = runState (execStateT (runMaybeT (go s)) Nothing) s
where
next = MaybeT . pure . LBS.uncons
update rest = do
lift $ lift $ put rest
pure rest
go bs = do
r <- next bs
void $ update (snd r)
exec r >>= update
>>= go
exec (b, rest)
| b == code @NOP = nop rest
| b == code @LOADB = loadb rest
| b == code @SKIPBI = skipbi rest
| b == code @ORBI = orbi rest
| b == code @ANDBI = andbi rest
| b == code @XORBI = xorbi rest
| b == code @ADDBI = addbi rest
| b == code @SUBBI = subbi rest
| b == code @MULTBI = multi rest
| b == code @RET = ret rest
-- | b == code @REPEAT = repeatN rest -- dangerous
| otherwise = nop rest
ret _ = mzero -- pure
nop = pure
multi bs = do
(n, rest) <- next bs
apply (*) n
pure rest
addbi bs = do
(n, rest) <- next bs
apply (+) n
pure rest
subbi bs = do
(n, rest) <- next bs
apply (-) n
pure rest
orbi bs = do
(n, rest) <- next bs
apply (.|.) n
pure rest
andbi bs = do
(n, rest) <- next bs
apply (.&.) n
pure rest
xorbi bs = do
(n, rest) <- next bs
apply xor n
pure rest
skipbi bs = do
(n, rest) <- next bs
let r2 = LBS.drop (fromIntegral n) rest
update r2
pure r2
loadb bs = do
(n, rest) <- next bs
put (Just n)
pure rest
repeatN bs = do
(n, rest) <- next bs
rest' <- replicateM (min 16 (fromIntegral n)) $ do
next rest >>= exec
pure (headDef "" rest')
apply fn n = do
st <- get
put $ Just $ fromMaybe 0 st `fn` fromIntegral n
code :: forall a b . (Integral b, Instruction a) => b
code = fromIntegral (natVal (Proxy @(Opcode a)))
class (Monad m) => RandomPrefix a m where
randomPrefix :: a -> m Builder
data PrefixMethod1 = PrefixMethod1 Int Word8 Word8
partsMethod1 :: Int -> Word8 -> Word8 -> [Word8]
partsMethod1 k b n = nums
where
(d, r) = n `divMod` b
nums = r : replicate (fromIntegral d) b & chunksOf k & fmap sum
instance MonadIO m => RandomPrefix PrefixMethod1 m where
randomPrefix (PrefixMethod1 k a x) = liftIO do
let nums = partsMethod1 k a x
me <- liftIO $ replicateM (length nums) $ randomRIO (0,2)
opcodes <- forM (zip me nums) $ \z@(_, n) ->
case fst z of
1 -> do
let (w,p) = n `divMod` 2
pure $ op @ADDBI p : replicate 2 (op @ADDBI w)
2 -> do
j <- randomIO @Word8
pure [ op @SUBBI j, op @ADDBI (n+j) ]
_ -> pure [ op @ADDBI n ]
sn <- randomRIO (1,6)
bytes <- replicateM sn (randomIO @Word8) <&> fmap byte
let fin = op @SKIPBI (fromIntegral sn) : bytes
pure $ emit $ mconcat opcodes <> fin <> [ op @RET () ]

View File

@ -29,6 +29,9 @@ import Data.ByteString (ByteString)
-- e -> Transport (like, UDP or TChan)
-- p -> L4 Protocol (like Ping/Pong)
class NonceFrom nonce a where
nonceFrom :: a -> nonce
data CryptoAction = Sign | Encrypt
data GroupKeyScheme = Symm | Asymm

View File

@ -1,4 +1,3 @@
{-# Language FunctionalDependencies #-}
module HBS2.Prelude
( module Data.String
, module Safe
@ -18,7 +17,7 @@ module HBS2.Prelude
, ToByteString(..)
, FromByteString(..)
, Text.Text
, (&), (<&>)
, (&), (<&>), for_, for
) where
import Data.Typeable as X
@ -32,6 +31,9 @@ import Control.Monad (guard,when,unless,MonadPlus(..))
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Maybe
import Data.Foldable(for_)
import Data.Traversable(for)
import Data.Kind
import Data.Function
import Data.Functor

View File

@ -0,0 +1,57 @@
module ByPassWorker where
import HBS2.Prelude
import HBS2.Clock
import HBS2.Actors.Peer
import HBS2.Net.Messaging.Encrypted.ByPass
import HBS2.System.Logger.Simple
import HBS2.Net.Proto.Peer
import HBS2.Net.Proto.PeerExchange
import HBS2.Net.Proto.Sessions
import HBS2.Net.Proto.Types
import PeerTypes
import Control.Monad
import UnliftIO
byPassWorker :: ( ForByPass e
, MonadUnliftIO m
, MonadIO m
, HasPeer e
, HasPeerLocator e m
, Sessions e (KnownPeer e) m
, Expires (SessionKey e (KnownPeer e))
)
=> ByPass e w
-> PeerEnv e
-> m ()
byPassWorker bp penv = do
tstat <- async $ forever do
stats <- getStat bp
info $ "ByPass stats"
<> line
<> indent 2 (pretty stats)
<> line
pause @'Seconds 60
link tstat
gc <- async $ withPeerM penv $ forever do
pips <- getKnownPeers
cleanupByPassMessaging bp pips
pause @'Seconds 600
link gc
void $ waitAnyCatchCancel [tstat, gc]

View File

@ -0,0 +1,70 @@
{-# Language UndecidableInstances #-}
module DispatchProxy
( RouteFun
, newDispatchProxy
, runDispatchProxy
, Dispatched(..)
) where
import HBS2.Prelude.Plated
import HBS2.Net.Messaging
import HBS2.System.Logger.Simple
import Control.Concurrent.STM.TQueue qualified as TQ
import Data.ByteString.Lazy (ByteString)
import Control.Monad
import UnliftIO
data Dispatched = forall bus . Messaging bus L4Proto ByteString => Dispatched bus
type RouteFun e = forall m . (MonadIO m)
=> ByteString
-> Peer e
-> m (Maybe Dispatched)
data DispatchProxy =
DispatchProxy
{ points :: [ (Peer L4Proto, Dispatched) ]
, route :: RouteFun L4Proto
, inbox :: TQueue (From L4Proto, ByteString)
}
newDispatchProxy :: (MonadIO m)
=> [ (Peer L4Proto, Dispatched) ]
-> RouteFun L4Proto
-> m DispatchProxy
newDispatchProxy p r = DispatchProxy p r <$> newTQueueIO
runDispatchProxy :: forall m . ( MonadUnliftIO m
)
=> DispatchProxy
-> m ()
runDispatchProxy d = do
debug "runDispatchProxy"
ps <- for (points d) $ \(pip, Dispatched mess) -> async do
forever do
receive mess (To pip)
>>= mapM_ (atomically . writeTQueue (inbox d))
mapM_ link ps
void $ waitAnyCatchCancel ps
instance Messaging DispatchProxy L4Proto ByteString where
sendTo bus t@(To whom) f m = do
route bus m whom >>= maybe none sendRouted
where
sendRouted (Dispatched target) = sendTo target t f m
receive bus _ = do
void $ atomically $ peekTQueue (inbox bus)
liftIO $ atomically $ TQ.flushTQueue (inbox bus)

View File

@ -46,8 +46,7 @@ downloadQueue _ brains denv = do
polling (Polling 5 20) refs $ \ref -> do
missed <- findMissedBlocks sto ref
debug $ "DownloadQ. check" <+> pretty ref <+> pretty (length missed)
trace $ "DownloadQ. check" <+> pretty ref <+> pretty (length missed)
when (null missed) do
delDownload @e brains ref

View File

@ -21,11 +21,10 @@ import HBS2.Net.IP.Addr
import HBS2.Net.Messaging.UDP
import HBS2.Net.Messaging.TCP
import HBS2.Net.Messaging.Unix
import HBS2.Net.Messaging.Encrypted.ByPass
import HBS2.Net.PeerLocator
import HBS2.Net.Proto as Proto
import HBS2.Net.Proto.Definition
-- import HBS2.Net.Proto.Dialog
import HBS2.Net.Proto.EncryptionHandshake
import HBS2.Net.Proto.Event.PeerExpired
import HBS2.Net.Proto.Peer
import HBS2.Net.Proto.PeerAnnounce
@ -43,6 +42,7 @@ import HBS2.System.Logger.Simple hiding (info)
import Brains
import BrainyPeerLocator
import ByPassWorker
import PeerTypes
import BlockDownload
import CheckBlockAnnounce (checkBlockAnnounce)
@ -52,13 +52,10 @@ import PeerInfo
import PeerConfig
import Bootstrap
import CheckMetrics
import EncryptionKeys
import RefLog qualified
import RefLog (reflogWorker)
import HttpWorker
import ProxyMessaging
-- import PeerMain.DialogCliCommand
-- import PeerMain.Dialog.Server
import DispatchProxy
import PeerMeta
import CLI.Common
import CLI.RefChan
@ -233,6 +230,7 @@ runCLI = do
<> command "download" (info pDownload (progDesc "download management"))
<> command "poll" (info pPoll (progDesc "polling management"))
<> command "log" (info pLog (progDesc "set logging level"))
<> command "bypass" (info pByPass (progDesc "bypass"))
-- FIXME: bring-back-dialogue-over-separate-socket
-- <> command "dial" (info pDialog (progDesc "dialog commands"))
)
@ -468,6 +466,16 @@ runCLI = do
delta = now - fromIntegral u
diff = formatTime defaultTimeLocale "%d:%H:%M:%S" delta
pByPass = hsubparser ( command "show" (info pByPassShow (progDesc "show bypass info" ))
)
pByPassShow = do
rpc <- pRpcCommon
pure $ withMyRPC @PeerAPI rpc $ \caller -> do
void $ runMaybeT do
d <- toMPlus =<< callService @RpcByPassInfo caller ()
liftIO $ print $ pretty d
refP :: ReadM (PubKey 'Sign HBS2Basic)
refP = maybeReader fromStringMay
@ -629,6 +637,8 @@ runPeer opts = U.handle (\e -> myException e
udp <- async $ runMessagingUDP mess
let udpAddr = getOwnPeer mess
mcast <- newMessagingUDPMulticast defLocalMulticast
`orDie` "Can't start RPC listener"
@ -652,43 +662,36 @@ runPeer opts = U.handle (\e -> myException e
void $ async $ runMessagingTCP tcpEnv
pure $ Just tcpEnv
(proxy, penv) <- mdo
proxy <- newProxyMessaging mess tcp >>= \proxy' -> pure proxy'
{ _proxy_getEncryptionKey = \peer -> do
mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $
withPeerM penv $ find (KnownPeerKey peer) id
mkey <- join <$> forM mencKeyID \encKeyID ->
getEncryptionKey proxy encKeyID
case mkey of
Nothing ->
trace1 $ "ENCRYPTION empty getEncryptionKey"
<+> pretty peer <+> viaShow mencKeyID
Just k ->
trace1 $ "ENCRYPTION success getEncryptionKey"
<+> pretty peer <+> viaShow mencKeyID <+> viaShow k
pure mkey
let mudp = Just $ Dispatched mess
, _proxy_clearEncryptionKey = \peer -> do
mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $
withPeerM penv $ find (KnownPeerKey peer) id
forM_ mencKeyID \encKeyID -> setEncryptionKey proxy peer encKeyID Nothing
-- deletePeerAsymmKey brains peer
forM_ mencKeyID \encKeyID ->
deletePeerAsymmKey' brains (show encKeyID)
let tcpaddr = view tcpOwnPeer <$> tcp
, _proxy_sendResetEncryptionKeys = \peer -> withPeerM penv do
sendResetEncryptionKeys peer
let mtcp = Dispatched <$> tcp
, _proxy_sendBeginEncryptionExchange = \peer -> withPeerM penv do
sendBeginEncryptionExchange pc
((pubKeyFromKeypair @s . _proxy_asymmetricKeyPair) proxy)
peer
let points = catMaybes [ (udpAddr ,) <$> mudp
, (,) <$> tcpaddr <*> mtcp
]
}
penv <- newPeerEnv pl (AnyStorage s) (Fabriq proxy) (getOwnPeer mess)
pure (proxy, penv)
proxy <- newDispatchProxy points $ \_ pip -> case view sockType pip of
TCP -> pure mtcp
UDP -> pure mudp
proxyThread <- async $ runProxyMessaging proxy
-- TODO: get-rid-of-from-addr
-- From addres в Messaging -- пережиток,
-- ни на что не влияет, ни для чего не нужен.
-- Таскается везде со времени, когда Messaging был
-- через TQueue. Нужно его удалить повсеместно
-- Или сделать некий AnyAddr/DefaultAddr
byPass <- newByPassMessaging @L4Proto
byPassDef
proxy
(getOwnPeer mess)
(view peerSignPk pc)
(view peerSignSk pc)
penv <- newPeerEnv pl (AnyStorage s) (Fabriq byPass) (getOwnPeer mess)
proxyThread <- async $ runDispatchProxy proxy
let peerMeta = mkPeerMeta conf penv
@ -733,8 +736,8 @@ runPeer opts = U.handle (\e -> myException e
if pro then do
withPeerM penv $ withDownload denv (addDownload mzero h)
else do
-- FIXME: separate-process-to-mark-logs-processed
withPeerM penv $ withDownload denv (processBlock h)
setReflogProcessed @e brains h
let doFetchRef puk = do
withPeerM penv $ do
@ -753,45 +756,6 @@ runPeer opts = U.handle (\e -> myException e
let hshakeAdapter = PeerHandshakeAdapter addNewRtt
let encryptionHshakeAdapter ::
( MonadIO m
, EventEmitter e (PeerAsymmInfo e) m
) => EncryptionHandshakeAdapter L4Proto m s
encryptionHshakeAdapter = EncryptionHandshakeAdapter
{ encHandshake_considerPeerAsymmKey = \peer mpubkey -> withPeerM penv do
mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $
withPeerM penv $ find (KnownPeerKey peer) id
case mpubkey of
Nothing -> do
-- trace $ "ENCRYPTION delete key" <+> pretty peer <+> viaShow mencKeyID
-- deletePeerAsymmKey brains peer
forM_ mencKeyID \encKeyID ->
deletePeerAsymmKey' brains (show encKeyID)
Just pk -> do
-- emit PeerAsymmInfoKey (PeerAsymmPubKey peer pk)
let symmk = genCommonSecret @s
(privKeyFromKeypair @s (_proxy_asymmetricKeyPair proxy))
pk
case mencKeyID of
Nothing -> do
-- insertPeerAsymmKey brains peer pk symmk
-- insertPeerAsymmKey' brains (show peer) pk symmk
trace $ "ENCRYPTION can not store key. No encKeyID"
<+> pretty peer <+> viaShow mencKeyID
Just encKeyID -> do
liftIO $ setEncryptionKey proxy peer encKeyID (Just symmk)
insertPeerAsymmKey' brains (show encKeyID) pk symmk
, encAsymmetricKeyPair = _proxy_asymmetricKeyPair proxy
, encGetEncryptionKey = liftIO . getEncryptionKey proxy
}
-- dialReqProtoAdapter <- do
-- dialReqProtoAdapterDApp <- pure dialogRoutes
-- pure DialReqProtoAdapter {..}
env <- ask
pnonce <- peerNonce @e
@ -800,14 +764,6 @@ runPeer opts = U.handle (\e -> myException e
addPeers @e pl ps
subscribe @e PeerExpiredEventKey \(PeerExpiredEvent peer {-mpeerData-}) -> liftIO do
mencKeyID <- (fmap . fmap) encryptionKeyIDKeyFromPeerData $
withPeerM penv $ find (KnownPeerKey peer) id
forM_ mencKeyID \encKeyID -> setEncryptionKey proxy peer encKeyID Nothing
-- deletePeerAsymmKey brains peer
forM_ mencKeyID \encKeyID ->
deletePeerAsymmKey' brains (show encKeyID)
subscribe @e PeerAnnounceEventKey $ \(PeerAnnounceEvent pip nonce) -> do
unless (nonce == pnonce) $ do
debug $ "Got peer announce!" <+> pretty pip
@ -936,6 +892,8 @@ runPeer opts = U.handle (\e -> myException e
debug "sending local peer announce"
request localMulticast (PeerAnnounce @e pnonce)
peerThread "byPassWorker" (byPassWorker byPass penv)
peerThread "httpWorker" (httpWorker conf peerMeta denv)
peerThread "checkMetrics" (checkMetrics metrics)
@ -952,14 +910,10 @@ runPeer opts = U.handle (\e -> myException e
peerThread "blockDownloadQ" (downloadQueue conf (SomeBrains brains) denv)
peerThread "encryptionHandshakeWorker"
(EncryptionKeys.encryptionHandshakeWorker @e conf pc encryptionHshakeAdapter)
peerThread "fillPeerMeta" (fillPeerMeta tcp tcpProbeWait)
peerThread "postponedLoop" (postponedLoop denv)
peerThread "reflogWorker" (reflogWorker @e conf (SomeBrains brains) rwa)
peerThread "refChanWorker" (refChanWorker @e rce (SomeBrains brains))
@ -970,7 +924,7 @@ runPeer opts = U.handle (\e -> myException e
, makeResponse (blockChunksProto adapter)
, makeResponse blockAnnounceProto
, makeResponse (withCredentials @e pc . peerHandShakeProto hshakeAdapter penv)
, makeResponse (withCredentials @e pc . encryptionHandshakeProto encryptionHshakeAdapter)
-- , makeResponse (withCredentials @e pc . encryptionHandshakeProto encryptionHshakeAdapter)
, makeResponse peerExchangeProto
, makeResponse refLogUpdateProto
, makeResponse (refLogRequestProto reflogReqAdapter)
@ -1067,6 +1021,7 @@ runPeer opts = U.handle (\e -> myException e
, rpcLocalMultiCast = localMulticast
, rpcStorage = AnyStorage s
, rpcBrains = SomeBrains brains
, rpcByPassInfo = liftIO (getStat byPass)
, rpcDoFetch = liftIO . fetchHash penv denv
, rpcDoRefChanHeadPost = refChanHeadPostAction
, rpcDoRefChanPropose = refChanProposeAction
@ -1084,6 +1039,7 @@ runPeer opts = U.handle (\e -> myException e
, makeResponse (makeServer @StorageAPI)
]
link proxyThread
link rpcProto
link loop
@ -1093,6 +1049,7 @@ runPeer opts = U.handle (\e -> myException e
, rpcProto
, ann
, messMcast
, proxyThread
, brainsThread
]

View File

@ -3,7 +3,10 @@
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
{-# Language MultiWayIf #-}
module PeerTypes where
module PeerTypes
( module PeerTypes
, module HBS2.Net.PeerLocator
) where
import HBS2.Actors.Peer
import HBS2.Clock

View File

@ -1,260 +0,0 @@
{-# Language TemplateHaskell #-}
module ProxyMessaging
( ProxyMessaging(..)
, newProxyMessaging
, runProxyMessaging
, sendToPlainProxyMessaging
, getEncryptionKey
, setEncryptionKey
, encryptionKeyIDKeyFromPeerData
) where
import HBS2.Prelude.Plated
import HBS2.Net.Messaging
import HBS2.Clock
import HBS2.Crypto
import HBS2.Net.Auth.Credentials
import HBS2.Net.Proto.Definition ()
import HBS2.Net.Proto.Peer
import HBS2.Net.Proto.Types
import HBS2.Net.Messaging.UDP
import HBS2.Net.Messaging.TCP
import HBS2.System.Logger.Simple
import PeerTypes
import Crypto.Saltine.Class as SCl
import Crypto.Saltine.Core.Box qualified as Encrypt
import Codec.Serialise
import Control.Applicative
import Control.Arrow hiding ((<+>))
import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Concurrent.STM.TQueue
import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.Hashable hiding (Hashed)
import Data.Maybe
import Data.String.Conversions (cs)
import Data.List qualified as L
import Data.Map (Map)
import Data.Map qualified as Map
import Lens.Micro.Platform as Lens
import Control.Monad
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import HBS2.Data.Types.Peer
-- TODO: protocol-encryption-goes-here
data ProxyMessaging =
ProxyMessaging
{ _proxyUDP :: MessagingUDP
, _proxyTCP :: Maybe MessagingTCP
, _proxyAnswers :: TQueue (From L4Proto, LBS.ByteString)
, _proxy_getEncryptionKey :: Peer L4Proto -> IO (Maybe (CommonSecret (Encryption L4Proto)))
, _proxy_clearEncryptionKey :: Peer L4Proto -> IO ()
, _proxy_sendResetEncryptionKeys :: Peer L4Proto -> IO ()
, _proxy_sendBeginEncryptionExchange :: Peer L4Proto -> IO ()
, _proxy_asymmetricKeyPair :: AsymmKeypair (Encryption L4Proto)
, _proxy_encryptionKeys :: TVar (HashMap (EncryptionKeyIDKey L4Proto) (CommonSecret (Encryption L4Proto)))
}
-- 1 нода X создаёт себе Encrypt.Keypair
-- 2 подписывает из него публичный ключ ключом подписи ноды X и отправляет ноде Y
-- 3 нода Y получила Публичный ключ ноды X, создала симметричный Key,
-- зашифровала его для полученного Публичного ключа ноды X и отравила ей
makeLenses 'ProxyMessaging
newProxyMessaging :: forall m . MonadIO m
=> MessagingUDP
-> Maybe MessagingTCP
-> m ProxyMessaging
newProxyMessaging u t = liftIO do
let _proxyUDP = u
let _proxyTCP = t
_proxyAnswers <- newTQueueIO
let _proxy_getEncryptionKey = const (pure Nothing)
let _proxy_clearEncryptionKey = const (pure ())
let _proxy_sendResetEncryptionKeys = const (pure ())
let _proxy_sendBeginEncryptionExchange = const (pure ())
_proxy_asymmetricKeyPair <- asymmNewKeypair @(Encryption L4Proto)
_proxy_encryptionKeys <- liftIO (newTVarIO mempty)
pure ProxyMessaging {..}
---
setEncryptionKey ::
( Hashable (PubKey 'Sign (Encryption L4Proto))
, Hashable PeerNonce
, Show (PubKey 'Sign (Encryption L4Proto))
, Show PeerNonce
, Show (CommonSecret (Encryption L4Proto))
, Show (EncryptionKeyIDKey L4Proto)
) => ProxyMessaging -> Peer L4Proto -> EncryptionKeyIDKey L4Proto -> Maybe (CommonSecret (Encryption L4Proto)) -> IO ()
setEncryptionKey proxy peer pd msecret = do
atomically $ modifyTVar' (_proxy_encryptionKeys proxy) $ Lens.at pd .~ msecret
case msecret of
Nothing -> trace $ "ENCRYPTION delete key" <+> pretty peer <+> viaShow pd
Just k -> trace $ "ENCRYPTION store key" <+> pretty peer <+> viaShow pd <+> viaShow k
getEncryptionKey ::
( Hashable (PubKey 'Sign (Encryption L4Proto))
, Hashable PeerNonce
) => ProxyMessaging -> EncryptionKeyIDKey L4Proto -> IO (Maybe (CommonSecret (Encryption L4Proto)))
getEncryptionKey proxy pd =
readTVarIO (_proxy_encryptionKeys proxy) <&> preview (Lens.ix pd)
---
runProxyMessaging :: forall m . MonadIO m
=> ProxyMessaging
-> m ()
runProxyMessaging env = liftIO do
let udp = view proxyUDP env
let answ = view proxyAnswers env
let udpPeer = getOwnPeer udp
u <- async $ forever do
msgs <- receive udp (To udpPeer)
atomically $ do
forM_ msgs $ writeTQueue answ
t <- async $ maybe1 (view proxyTCP env) none $ \tcp -> do
forever do
msgs <- receive tcp (To $ view tcpOwnPeer tcp)
atomically $ do
forM_ msgs $ writeTQueue answ
liftIO $ mapM_ waitCatch [u,t]
instance Messaging ProxyMessaging L4Proto LBS.ByteString where
sendTo = sendToProxyMessaging
receive = receiveFromProxyMessaging
-- receive bus _ = liftIO do
-- -- trace "PROXY: RECEIVE"
-- -- receive (view proxyUDP bus) w
-- let answ = view proxyAnswers bus
-- atomically $ do
-- r <- readTQueue answ
-- rs <- flushTQueue answ
-- pure (r:rs)
sendToPlainProxyMessaging :: (MonadIO m)
=> ProxyMessaging
-> To L4Proto
-> From L4Proto
-> LBS.ByteString
-> m ()
sendToPlainProxyMessaging bus t@(To whom) proto msg = do
let udp = view proxyUDP bus
case view sockType whom of
UDP -> sendTo udp t proto msg
TCP -> maybe1 (view proxyTCP bus) none $ \tcp -> do
sendTo tcp t proto msg
sendToProxyMessaging :: (MonadIO m)
=> ProxyMessaging
-> To L4Proto
-> From L4Proto
-> LBS.ByteString
-> m ()
sendToProxyMessaging bus t@(To whom) proto msg = do
-- sendTo (view proxyUDP bus) t proto msg
-- trace $ "PROXY: SEND" <+> pretty whom
mencKey <- liftIO $ _proxy_getEncryptionKey bus whom
cf <- case mencKey of
Nothing -> do
trace1 $ "ENCRYPTION SEND: sending plain message to" <+> pretty whom
pure id
Just k -> do
trace1 $ "ENCRYPTION SEND: sending encrypted message to" <+> pretty whom <+> "with key" <+> viaShow k
boxAfterNMLazy k <$> liftIO Encrypt.newNonce
sendToPlainProxyMessaging bus t proto (cf msg)
receiveFromProxyMessaging :: MonadIO m
=> ProxyMessaging -> To L4Proto -> m [(From L4Proto, LBS.ByteString)]
receiveFromProxyMessaging bus _ = liftIO do
-- trace "PROXY: RECEIVE"
-- receive (view proxyUDP bus) w
let answ = view proxyAnswers bus
rs <- atomically $ liftM2 (:) (readTQueue answ) (flushTQueue answ)
catMaybes <$> forM rs \(w@(From whom), msg) -> do
fmap (w, ) <$> dfm whom msg
-- Здесь:
-- 1. У нас есть ключ сессии и мы не смогли расшифровать -> do
-- удаляем у себя ключ
-- отправляем sendBeginEncryptionExchange
-- 2. У нас (до сих пор, даже если мы давно стартовали) нет ключа сессии -> do
-- sendResetEncryptionKeys
-- просто передаём сообщение как есть
-- В протоколе пингов:
-- 1. Если слишком долго нет ответа на ping, то удаляем у себя ключ, отправляем sendResetEncryptionKeys
-- Выполняется в PeerInfo:
-- emit PeerExpiredEventKey (PeerExpiredEvent @e p mpeerData)
where
dfm :: Peer L4Proto -> LBS.ByteString -> IO (Maybe LBS.ByteString)
dfm = \whom msg -> liftIO $ _proxy_getEncryptionKey bus whom >>= \case
Nothing -> do
trace1 $ "ENCRYPTION RECEIVE: we do not have a key to decode" <+> pretty whom
liftIO $ _proxy_sendBeginEncryptionExchange bus whom
pure (Just msg)
Just k -> runMaybeT $
-- А будем-ка мы просто передавать сообщение дальше как есть, если не смогли расшифровать
(<|> (do
liftIO $ _proxy_clearEncryptionKey bus whom
liftIO $ _proxy_sendResetEncryptionKeys bus whom
trace1 $ "ENCRYPTION RECEIVE: got plain message. clearing key of" <+> pretty whom
pure msg
)) $
do
trace $ "ENCRYPTION RECEIVE: we have a key to decode from" <+> pretty whom <+> ":" <+> viaShow k
case ((extractNonce . cs) msg) of
Nothing -> do
trace1 $ "ENCRYPTION RECEIVE: can not extract nonce from" <+> pretty whom <+> "message" <+> viaShow msg
fail ""
Just (nonce, msg') ->
((MaybeT . pure) (boxOpenAfterNMLazy k nonce msg')
<* (trace1 $ "ENCRYPTION RECEIVE: message successfully decoded from" <+> pretty whom)
)
<|>
(do
(trace1 $ "ENCRYPTION RECEIVE: can not decode message from" <+> pretty whom)
fail ""
-- -- Попытаться десериализовать сообщение как PeerPing или PeerPingCrypted
-- case deserialiseOrFail msg of
-- Right (_ :: PeerHandshake L4Proto) -> do
-- trace $ "ENCRYPTION RECEIVE: plain message decoded as PeerHandshake" <+> pretty whom
-- fail ""
-- Left _ -> do
-- trace $ "ENCRYPTION RECEIVE: failed" <+> pretty whom
-- mzero
)

View File

@ -0,0 +1,21 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE UndecidableInstances #-}
module RPC2.ByPassStat where
import HBS2.Prelude.Plated
import HBS2.Net.Proto.Service
import HBS2.System.Logger.Simple
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.Internal.Types
instance (MonadIO m, HasRpcContext PeerAPI RPC2Context m) => HandleMethod m RpcByPassInfo where
handleMethod _ = do
co <- getRpcContext @PeerAPI
debug $ "rpc.byPassInfo"
liftIO $ rpcByPassInfo co

View File

@ -17,4 +17,6 @@ import RPC2.Die()
import RPC2.LogLevel()
import RPC2.Poll()
import RPC2.Downloads()
import RPC2.ByPassStat()

View File

@ -155,9 +155,9 @@ executable hbs2-peer
other-modules:
BlockDownload
, BrainyPeerLocator
, ByPassWorker
, DownloadQ
, DownloadMon
, EncryptionKeys
, Bootstrap
, PeerInfo
, PeerMain.Dialog.Server
@ -174,6 +174,7 @@ executable hbs2-peer
, RPC2.Announce
, RPC2.Fetch
, RPC2.Die
, RPC2.ByPassStat
, RPC2.LogLevel
, RPC2.Peers
, RPC2.PexInfo
@ -189,7 +190,7 @@ executable hbs2-peer
, CheckMetrics
, HttpWorker
, Brains
, ProxyMessaging
, DispatchProxy
, CLI.Common
, CLI.RefChan

View File

@ -6,6 +6,7 @@ import HBS2.Net.Messaging.Unix
import HBS2.Net.Proto.Service
import HBS2.Data.Types.Refs (HashRef(..))
import HBS2.Actors.Peer
import HBS2.Net.Messaging.Encrypted.ByPass(ByPassStat)
import HBS2.Peer.RPC.Internal.Types
@ -30,6 +31,8 @@ data RpcPollDel
data RpcDownloadList
data RpcDownloadDel
data RpcByPassInfo
type PeerAPI = '[ RpcPoke
, RpcPing
, RpcAnnounce
@ -43,6 +46,7 @@ type PeerAPI = '[ RpcPoke
, RpcPollDel
, RpcDownloadList
, RpcDownloadDel
, RpcByPassInfo
]
instance HasProtocol UNIX (ServiceProto PeerAPI UNIX) where
@ -95,6 +99,9 @@ type instance Output RpcPollDel = ()
type instance Input RpcLogLevel = SetLogging
type instance Output RpcLogLevel = ()
type instance Input RpcByPassInfo = ()
type instance Output RpcByPassInfo = ByPassStat
data SetLogging =
DebugOn Bool
| TraceOn Bool

View File

@ -6,10 +6,11 @@ module HBS2.Peer.RPC.Internal.Types
import HBS2.Actors.Peer
import HBS2.Net.Proto.Types
import HBS2.Storage
import HBS2.Storage()
import HBS2.Data.Types.Refs (HashRef)
import HBS2.Data.Types.SignedBox
import HBS2.Net.Messaging.Unix
import HBS2.Net.Messaging.Encrypted.ByPass (ByPassStat)
import HBS2.Net.Proto.Service
import HBS2.Peer.RPC.Class
import HBS2.Peer.Brains
@ -31,6 +32,7 @@ data RPC2Context =
, rpcLocalMultiCast :: Peer L4Proto
, rpcStorage :: AnyStorage
, rpcBrains :: SomeBrains L4Proto
, rpcByPassInfo :: IO ByPassStat
, rpcDoFetch :: HashRef -> IO ()
, rpcDoRefChanHeadPost :: HashRef -> IO ()
, rpcDoRefChanPropose :: (PubKey 'Sign HBS2Basic, SignedBox ByteString L4Proto) -> IO ()

View File

@ -751,6 +751,60 @@ executable topsort-commits
-- , vector
-- , fast-logger
executable test-enc-1
import: shared-properties
import: common-deps
default-language: Haskell2010
ghc-options:
-- -prof
-- -fprof-auto
other-modules:
-- other-extensions:
-- type: exitcode-stdio-1.0
hs-source-dirs: test
main-is: TestEnc.hs
build-depends:
base, hbs2-core
-- , async
-- , attoparsec
, bytestring
-- , cache
-- , clock
, containers
, interpolatedstring-perl6
-- , data-default
-- , data-textual
-- , directory
-- , hashable
-- , microlens-platform
-- , mtl
-- , mwc-random
, memory
, network-byte-order
-- , network
-- , network-ip
, prettyprinter
-- , random
, safe
, serialise
-- , stm
-- , streaming
, saltine
, text
, time
-- , typed-process
-- , transformers
, uniplate
, unliftio
-- , vector
-- , fast-logger
executable create-raw-tx
import: shared-properties

238
hbs2-tests/test/TestEnc.hs Normal file
View File

@ -0,0 +1,238 @@
{-# Language TemplateHaskell #-}
module Main where
import HBS2.Prelude.Plated
import HBS2.Clock
import HBS2.Net.Proto
import HBS2.Net.Messaging.UDP
import HBS2.Net.Messaging.TCP
import HBS2.Actors.Peer
import HBS2.OrDie
import HBS2.System.Logger.Simple
import HBS2.Net.Messaging.Encrypted.ByPass
import Control.Monad.Reader
import Data.ByteString.Lazy (ByteString)
import Prettyprinter
import System.IO
import Lens.Micro.Platform
import Crypto.Saltine.Core.Sign
import Data.Int
import Codec.Serialise
-- import Control.Concurrent.Async
import System.Random
import Text.InterpolatedString.Perl6 (qc)
import Control.Monad.Trans.Resource
import UnliftIO.Async
type UDP = L4Proto
data PingPong e = Ping Int
| Pong Int
| Poke
deriving stock (Eq,Generic,Show,Read)
instance Serialise (PingPong e)
instance HasProtocol UDP (PingPong UDP) where
type instance ProtocolId (PingPong UDP) = 1307114574
type instance Encoded UDP = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
pingPongHandler :: forall e m . ( MonadIO m
, Response e (PingPong e) m
, HasProtocol e (PingPong e)
, HasOwnPeer e m
, Pretty (Peer e)
)
=> Int
-> PingPong e
-> m ()
pingPongHandler n = \case
Poke -> pure ()
Ping c -> do
self <- ownPeer @e
debug ("Ping" <+> pretty self <+> pretty c) >> response (Pong @e c)
Pong c | c < n -> do
self <- ownPeer @e
debug ("Pong" <+> pretty self <+> pretty c) >> response (Ping @e (succ c))
| otherwise -> pure ()
data PPEnv =
PPEnv
{ _ppSelf :: Peer UDP
, _ppFab :: Fabriq UDP
}
makeLenses 'PPEnv
newtype PingPongM m a = PingPongM { fromPingPong :: ReaderT PPEnv m a }
deriving newtype ( Functor
, Applicative
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader PPEnv
, MonadTrans
)
runPingPong :: (MonadIO m) => Peer UDP -> Fabriq UDP -> PingPongM m a -> m a
runPingPong pip udp m = runReaderT (fromPingPong m) (PPEnv pip udp)
instance Monad m => HasFabriq UDP (PingPongM m) where
getFabriq = asks (view ppFab)
instance Monad m => HasOwnPeer UDP (PingPongM m) where
ownPeer = asks (view ppSelf)
instance HasTimeLimits UDP (PingPong UDP) IO where
tryLockForPeriod _ _ = pure True
tracePrefix :: SetLoggerEntry
tracePrefix = logPrefix "[trace] "
debugPrefix :: SetLoggerEntry
debugPrefix = logPrefix "[debug] "
errorPrefix :: SetLoggerEntry
errorPrefix = logPrefix "[error] "
warnPrefix :: SetLoggerEntry
warnPrefix = logPrefix "[warn] "
noticePrefix :: SetLoggerEntry
noticePrefix = logPrefix "[RT] "
testUDP :: IO ()
testUDP = runResourceT do
let o = byPassDef
udp1 <- newMessagingUDP False (Just "127.0.0.1:10001") `orDie` "Can't start listener on 10001"
udp2 <- newMessagingUDP False (Just "127.0.0.1:10002") `orDie` "Can't start listener on 10002"
Keypair s1 p1 <- liftIO newKeypair
pass1 <- newByPassMessaging @UDP o udp1 (getOwnPeer udp1) p1 s1
Keypair s2 p2 <- liftIO newKeypair
pass2 <- newByPassMessaging @UDP o udp2 (getOwnPeer udp2) p2 s2
m1 <- async $ runMessagingUDP udp1
m2 <- async $ runMessagingUDP udp2
let own1 = getOwnPeer udp1
let own2 = getOwnPeer udp2
peer1 <- async $ runPingPong own1 (Fabriq pass1) do
proto <- async $ runProto @UDP
[ makeResponse (pingPongHandler 10)
]
link proto
forever (pause @'Seconds 10)
peer2 <- async $ runPingPong own2 (Fabriq pass2) do
proto <- async $ runProto @UDP
[ makeResponse (pingPongHandler 10)
]
link proto
request (getOwnPeer udp1) (Ping @UDP 0)
forever (pause @'Seconds 10)
mapM_ wait [peer1,peer2,m1,m2]
testTCP :: IO ()
testTCP = runResourceT do
let o = byPassDef
pn1 <- liftIO $ randomIO @Int8 <&> ((11000 +) . fromIntegral)
pn2 <- liftIO $ randomIO @Int8 <&> ((11000 +). fromIntegral)
let addr1 = fromString [qc|tcp://127.0.0.1:{pn1}|]
let addr2 = fromString [qc|tcp://127.0.0.1:{pn2}|]
debug $ "ADDR1" <+> pretty addr1
debug $ "ADDR2" <+> pretty addr2
me1 <- newMessagingTCP addr1
me2 <- newMessagingTCP addr2
m1 <- async $ runMessagingTCP me1
m2 <- async $ runMessagingTCP me2
let peer1 = view tcpOwnPeer me1
let peer2 = view tcpOwnPeer me2
Keypair s1 p1 <- liftIO newKeypair
pass1 <- newByPassMessaging o me1 peer1 p1 s1
Keypair s2 p2 <- liftIO newKeypair
pass2 <- newByPassMessaging o me2 peer2 p2 s2
peerThread1 <- async $ runPingPong peer1 (Fabriq pass1) do
proto <- async $ runProto @L4Proto
[ makeResponse (pingPongHandler 10)
]
link proto
request peer2 (Poke @L4Proto)
pause @'Seconds 1
request peer2 (Ping @L4Proto 0)
forever (pause @Seconds 10)
peerThread2 <- async $ runPingPong peer2 (Fabriq pass2) do
proto <- async $ runProto @L4Proto
[ makeResponse (pingPongHandler 10)
]
link proto
forever (pause @Seconds 10)
waiter <- async $ do
pause @'Seconds 3
void $ waitAnyCatchCancel [peerThread1,peerThread2,m1,m2,waiter]
stat1 <- getStat pass1
stat2 <- getStat pass2
debug "testUDP done"
liftIO $ print $ "peer1 stats" <> line <> indent 4 (pretty stat1) <> line
liftIO $ print $ "peer2 stats" <> line <> indent 4 (pretty stat2) <> line
main :: IO ()
main = do
liftIO $ hSetBuffering stdout LineBuffering
liftIO $ hSetBuffering stderr LineBuffering
setLogging @DEBUG debugPrefix
setLogging @INFO defLog
setLogging @ERROR errorPrefix
setLogging @WARN warnPrefix
setLogging @NOTICE noticePrefix
setLogging @TRACE tracePrefix
testTCP

View File

@ -210,7 +210,7 @@ main = do
]
pp2 <- async $ runPingPong env2 do
-- request (view tcpOwnPeer env1) (Ping @L4Proto 1)
request (view tcpOwnPeer env1) (Ping @L4Proto 1)
runProto @L4Proto
[ makeResponse (pingPongHandler 3)
]