agent: quantum-resistant double ratchet encryption (#939)

* doc

* diff

* ratchet header

* types

* ratchet step with PQ KEM, message header with KEM

* comment

* update types, remove Eq instances, store KEM keys to database

* pqx3dh

* PQ double ratchet test

* pqdr tests pass

* fix most tests

* refactor

* allow KEM proposals from both sides

* test names

* agent API parameters to use PQ KEM

* initialize ratchet state for enabling KEM

* fix/test KEM state machine to support disabling/enabling via messages

* more tests

* diff

* diff2

* refactor

* refactor

* refactor

* refactor

* remove Maybe

* rename

* add PQ encryption status to CON, MID and MSG events and sendMessage API results

* different PQ parameter when creating connection

* rename/reorganize types for PQ encryption modes

* rename

* fix testWaitDeliveryTimeout

* rename

* rename2

* ghc8107

* rename

* increase timeouts for concurrent send/receive test

* enable all tests

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
Evgeny Poberezkin
2024-03-03 19:40:49 +00:00
committed by GitHub
parent 30fd4065d9
commit e06e22328f
31 changed files with 1776 additions and 689 deletions
+534 -88
View File
@@ -5,16 +5,23 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- {-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
module Simplex.Messaging.Crypto.Ratchet where
import Control.Applicative ((<|>))
import Control.Monad.Except
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except
import Crypto.Cipher.AES (AES256)
import Crypto.Hash (SHA512)
@@ -23,22 +30,30 @@ import Crypto.Random (ChaChaDRG)
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.Attoparsec.ByteString (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteArray as BA
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as LB
import Data.Composition ((.:), (.:.))
import Data.Functor (($>))
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, isJust)
import Data.Type.Equality
import Data.Typeable (Typeable)
import Data.Word (Word32)
import Database.SQLite.Simple.FromField (FromField (..))
import Database.SQLite.Simple.ToField (ToField (..))
import Simplex.Messaging.Agent.QueryString
import Simplex.Messaging.Crypto
import Simplex.Messaging.Crypto.SNTRUP761.Bindings
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (blobFieldDecoder, defaultJSON, parseE, parseE')
import Simplex.Messaging.Util ((<$?>), ($>>=))
import Simplex.Messaging.Version
import UnliftIO.STM
@@ -49,74 +64,319 @@ import UnliftIO.STM
kdfX3DHE2EEncryptVersion :: Version
kdfX3DHE2EEncryptVersion = 2
pqRatchetVersion :: Version
pqRatchetVersion = 3
currentE2EEncryptVersion :: Version
currentE2EEncryptVersion = 2
currentE2EEncryptVersion = 3
supportedE2EEncryptVRange :: VersionRange
supportedE2EEncryptVRange = mkVersionRange kdfX3DHE2EEncryptVersion currentE2EEncryptVersion
data E2ERatchetParams (a :: Algorithm)
= E2ERatchetParams Version (PublicKey a) (PublicKey a)
deriving (Eq, Show)
data RatchetKEMState
= RKSProposed -- only KEM encapsulation key
| RKSAccepted -- KEM ciphertext and the next encapsulation key
instance AlgorithmI a => Encoding (E2ERatchetParams a) where
smpEncode (E2ERatchetParams v k1 k2) = smpEncode (v, k1, k2)
smpP = E2ERatchetParams <$> smpP <*> smpP <*> smpP
data SRatchetKEMState (s :: RatchetKEMState) where
SRKSProposed :: SRatchetKEMState 'RKSProposed
SRKSAccepted :: SRatchetKEMState 'RKSAccepted
instance VersionI (E2ERatchetParams a) where
type VersionRangeT (E2ERatchetParams a) = E2ERatchetParamsUri a
version (E2ERatchetParams v _ _) = v
toVersionRangeT (E2ERatchetParams _ k1 k2) vr = E2ERatchetParamsUri vr k1 k2
deriving instance Show (SRatchetKEMState s)
instance VersionRangeI (E2ERatchetParamsUri a) where
type VersionT (E2ERatchetParamsUri a) = (E2ERatchetParams a)
versionRange (E2ERatchetParamsUri vr _ _) = vr
toVersionT (E2ERatchetParamsUri _ k1 k2) v = E2ERatchetParams v k1 k2
instance TestEquality SRatchetKEMState where
testEquality SRKSProposed SRKSProposed = Just Refl
testEquality SRKSAccepted SRKSAccepted = Just Refl
testEquality _ _ = Nothing
data E2ERatchetParamsUri (a :: Algorithm)
= E2ERatchetParamsUri VersionRange (PublicKey a) (PublicKey a)
deriving (Eq, Show)
class RatchetKEMStateI (s :: RatchetKEMState) where sRatchetKEMState :: SRatchetKEMState s
instance AlgorithmI a => StrEncoding (E2ERatchetParamsUri a) where
strEncode (E2ERatchetParamsUri vs key1 key2) =
strEncode $
QSP QNoEscaping [("v", strEncode vs), ("x3dh", strEncodeList [key1, key2])]
instance RatchetKEMStateI RKSProposed where sRatchetKEMState = SRKSProposed
instance RatchetKEMStateI RKSAccepted where sRatchetKEMState = SRKSAccepted
checkRatchetKEMState :: forall t s s' a. (RatchetKEMStateI s, RatchetKEMStateI s') => t s' a -> Either String (t s a)
checkRatchetKEMState x = case testEquality (sRatchetKEMState @s) (sRatchetKEMState @s') of
Just Refl -> Right x
Nothing -> Left "bad ratchet KEM state"
checkRatchetKEMState' :: forall t s s'. (RatchetKEMStateI s, RatchetKEMStateI s') => t s' -> Either String (t s)
checkRatchetKEMState' x = case testEquality (sRatchetKEMState @s) (sRatchetKEMState @s') of
Just Refl -> Right x
Nothing -> Left "bad ratchet KEM state"
data RKEMParams (s :: RatchetKEMState) where
RKParamsProposed :: KEMPublicKey -> RKEMParams 'RKSProposed
RKParamsAccepted :: KEMCiphertext -> KEMPublicKey -> RKEMParams 'RKSAccepted
deriving instance Show (RKEMParams s)
data ARKEMParams = forall s. RatchetKEMStateI s => ARKP (SRatchetKEMState s) (RKEMParams s)
deriving instance Show ARKEMParams
instance RatchetKEMStateI s => Encoding (RKEMParams s) where
smpEncode = \case
RKParamsProposed k -> smpEncode ('P', k)
RKParamsAccepted ct k -> smpEncode ('A', ct, k)
smpP = (\(ARKP _ ps) -> checkRatchetKEMState' ps) <$?> smpP
instance Encoding (ARKEMParams) where
smpEncode (ARKP _ ps) = smpEncode ps
smpP =
smpP >>= \case
'P' -> ARKP SRKSProposed . RKParamsProposed <$> smpP
'A' -> ARKP SRKSAccepted .: RKParamsAccepted <$> smpP <*> smpP
_ -> fail "bad ratchet KEM params"
data E2ERatchetParams (s :: RatchetKEMState) (a :: Algorithm)
= E2ERatchetParams Version (PublicKey a) (PublicKey a) (Maybe (RKEMParams s))
deriving (Show)
data AE2ERatchetParams (a :: Algorithm)
= forall s.
RatchetKEMStateI s =>
AE2ERatchetParams (SRatchetKEMState s) (E2ERatchetParams s a)
deriving instance Show (AE2ERatchetParams a)
data AnyE2ERatchetParams
= forall s a.
(RatchetKEMStateI s, DhAlgorithm a, AlgorithmI a) =>
AnyE2ERatchetParams (SRatchetKEMState s) (SAlgorithm a) (E2ERatchetParams s a)
deriving instance Show AnyE2ERatchetParams
instance (RatchetKEMStateI s, AlgorithmI a) => Encoding (E2ERatchetParams s a) where
smpEncode (E2ERatchetParams v k1 k2 kem_)
| v >= pqRatchetVersion = smpEncode (v, k1, k2, kem_)
| otherwise = smpEncode (v, k1, k2)
smpP = toParams <$?> smpP
where
toParams :: AE2ERatchetParams a -> Either String (E2ERatchetParams s a)
toParams = \case
AE2ERatchetParams _ (E2ERatchetParams v k1 k2 Nothing) -> Right $ E2ERatchetParams v k1 k2 Nothing
AE2ERatchetParams _ ps -> checkRatchetKEMState ps
instance AlgorithmI a => Encoding (AE2ERatchetParams a) where
smpEncode (AE2ERatchetParams _ ps) = smpEncode ps
smpP = (\(AnyE2ERatchetParams s _ ps) -> (AE2ERatchetParams s) <$> checkAlgorithm ps) <$?> smpP
instance Encoding AnyE2ERatchetParams where
smpEncode (AnyE2ERatchetParams _ _ ps) = smpEncode ps
smpP = do
v :: Version <- smpP
APublicDhKey a k1 <- smpP
APublicDhKey a' k2 <- smpP
case testEquality a a' of
Nothing -> fail "bad e2e params: different key algorithms"
Just Refl ->
kemP v >>= \case
Just (ARKP s kem) -> pure $ AnyE2ERatchetParams s a $ E2ERatchetParams v k1 k2 (Just kem)
Nothing -> pure $ AnyE2ERatchetParams SRKSProposed a $ E2ERatchetParams v k1 k2 Nothing
where
kemP :: Version -> Parser (Maybe (ARKEMParams))
kemP v
| v >= pqRatchetVersion = smpP
| otherwise = pure Nothing
instance VersionI (E2ERatchetParams s a) where
type VersionRangeT (E2ERatchetParams s a) = E2ERatchetParamsUri s a
version (E2ERatchetParams v _ _ _) = v
toVersionRangeT (E2ERatchetParams _ k1 k2 kem_) vr = E2ERatchetParamsUri vr k1 k2 kem_
instance VersionRangeI (E2ERatchetParamsUri s a) where
type VersionT (E2ERatchetParamsUri s a) = (E2ERatchetParams s a)
versionRange (E2ERatchetParamsUri vr _ _ _) = vr
toVersionT (E2ERatchetParamsUri _ k1 k2 kem_) v = E2ERatchetParams v k1 k2 kem_
type RcvE2ERatchetParamsUri a = E2ERatchetParamsUri 'RKSProposed a
data E2ERatchetParamsUri (s :: RatchetKEMState) (a :: Algorithm)
= E2ERatchetParamsUri VersionRange (PublicKey a) (PublicKey a) (Maybe (RKEMParams s))
deriving (Show)
data AE2ERatchetParamsUri (a :: Algorithm)
= forall s.
RatchetKEMStateI s =>
AE2ERatchetParamsUri (SRatchetKEMState s) (E2ERatchetParamsUri s a)
deriving instance Show (AE2ERatchetParamsUri a)
data AnyE2ERatchetParamsUri
= forall s a.
(RatchetKEMStateI s, DhAlgorithm a, AlgorithmI a) =>
AnyE2ERatchetParamsUri (SRatchetKEMState s) (SAlgorithm a) (E2ERatchetParamsUri s a)
deriving instance Show AnyE2ERatchetParamsUri
instance (RatchetKEMStateI s, AlgorithmI a) => StrEncoding (E2ERatchetParamsUri s a) where
strEncode (E2ERatchetParamsUri vs key1 key2 kem_) =
strEncode . QSP QNoEscaping $
[("v", strEncode vs), ("x3dh", strEncodeList [key1, key2])]
<> maybe [] encodeKem kem_
where
encodeKem kem
| maxVersion vs < pqRatchetVersion = []
| otherwise = case kem of
RKParamsProposed k -> [("kem_key", strEncode k)]
RKParamsAccepted ct k -> [("kem_ct", strEncode ct), ("kem_key", strEncode k)]
strP = toParamsURI <$?> strP
where
toParamsURI = \case
AE2ERatchetParamsUri _ (E2ERatchetParamsUri vr k1 k2 Nothing) -> Right $ E2ERatchetParamsUri vr k1 k2 Nothing
AE2ERatchetParamsUri _ ps -> checkRatchetKEMState ps
instance AlgorithmI a => StrEncoding (AE2ERatchetParamsUri a) where
strEncode (AE2ERatchetParamsUri _ ps) = strEncode ps
strP = (\(AnyE2ERatchetParamsUri s _ ps) -> (AE2ERatchetParamsUri s) <$> checkAlgorithm ps) <$?> strP
instance StrEncoding AnyE2ERatchetParamsUri where
strEncode (AnyE2ERatchetParamsUri _ _ ps) = strEncode ps
strP = do
query <- strP
vs <- queryParam "v" query
vr :: VersionRange <- queryParam "v" query
keys <- L.toList <$> queryParam "x3dh" query
case keys of
[key1, key2] -> pure $ E2ERatchetParamsUri vs key1 key2
[APublicDhKey a k1, APublicDhKey a' k2] -> case testEquality a a' of
Nothing -> fail "bad e2e params: different key algorithms"
Just Refl ->
kemP vr query >>= \case
Just (ARKP s kem) -> pure $ AnyE2ERatchetParamsUri s a $ E2ERatchetParamsUri vr k1 k2 (Just kem)
Nothing -> pure $ AnyE2ERatchetParamsUri SRKSProposed a $ E2ERatchetParamsUri vr k1 k2 Nothing
_ -> fail "bad e2e params"
where
kemP vr query
| maxVersion vr >= pqRatchetVersion =
queryParam_ "kem_key" query
$>>= \k -> (Just . kemParams k <$> queryParam_ "kem_ct" query)
| otherwise = pure Nothing
kemParams k = \case
Nothing -> ARKP SRKSProposed $ RKParamsProposed k
Just ct -> ARKP SRKSAccepted $ RKParamsAccepted ct k
generateE2EParams :: (AlgorithmI a, DhAlgorithm a) => TVar ChaChaDRG -> Version -> STM (PrivateKey a, PrivateKey a, E2ERatchetParams a)
generateE2EParams g v = do
(k1, pk1) <- generateKeyPair g
(k2, pk2) <- generateKeyPair g
pure (pk1, pk2, E2ERatchetParams v k1 k2)
type RcvE2ERatchetParams a = E2ERatchetParams 'RKSProposed a
type SndE2ERatchetParams a = AE2ERatchetParams a
data PrivRKEMParams (s :: RatchetKEMState) where
PrivateRKParamsProposed :: KEMKeyPair -> PrivRKEMParams 'RKSProposed
PrivateRKParamsAccepted :: KEMCiphertext -> KEMSharedKey -> KEMKeyPair -> PrivRKEMParams 'RKSAccepted
data APrivRKEMParams = forall s. RatchetKEMStateI s => APRKP (SRatchetKEMState s) (PrivRKEMParams s)
type RcvPrivRKEMParams = PrivRKEMParams 'RKSProposed
instance RatchetKEMStateI s => Encoding (PrivRKEMParams s) where
smpEncode = \case
PrivateRKParamsProposed k -> smpEncode ('P', k)
PrivateRKParamsAccepted ct shared k -> smpEncode ('A', ct, shared, k)
smpP = (\(APRKP _ ps) -> checkRatchetKEMState' ps) <$?> smpP
instance Encoding (APrivRKEMParams) where
smpEncode (APRKP _ ps) = smpEncode ps
smpP =
smpP >>= \case
'P' -> APRKP SRKSProposed . PrivateRKParamsProposed <$> smpP
'A' -> APRKP SRKSAccepted .:. PrivateRKParamsAccepted <$> smpP <*> smpP <*> smpP
_ -> fail "bad APrivRKEMParams"
instance RatchetKEMStateI s => ToField (PrivRKEMParams s) where toField = toField . smpEncode
instance (Typeable s, RatchetKEMStateI s) => FromField (PrivRKEMParams s) where fromField = blobFieldDecoder smpDecode
data UseKEM (s :: RatchetKEMState) where
ProposeKEM :: UseKEM 'RKSProposed
AcceptKEM :: KEMPublicKey -> UseKEM 'RKSAccepted
data AUseKEM = forall s. RatchetKEMStateI s => AUseKEM (SRatchetKEMState s) (UseKEM s)
generateE2EParams :: forall s a. (AlgorithmI a, DhAlgorithm a) => TVar ChaChaDRG -> Version -> Maybe (UseKEM s) -> IO (PrivateKey a, PrivateKey a, Maybe (PrivRKEMParams s), E2ERatchetParams s a)
generateE2EParams g v useKEM_ = do
(k1, pk1) <- atomically $ generateKeyPair g
(k2, pk2) <- atomically $ generateKeyPair g
kems <- kemParams
pure (pk1, pk2, snd <$> kems, E2ERatchetParams v k1 k2 (fst <$> kems))
where
kemParams :: IO (Maybe (RKEMParams s, PrivRKEMParams s))
kemParams = case useKEM_ of
Just useKem | v >= pqRatchetVersion -> Just <$> do
ks@(k, _) <- sntrup761Keypair g
case useKem of
ProposeKEM -> pure (RKParamsProposed k, PrivateRKParamsProposed ks)
AcceptKEM k' -> do
(ct, shared) <- sntrup761Enc g k'
pure (RKParamsAccepted ct k, PrivateRKParamsAccepted ct shared ks)
_ -> pure Nothing
-- used by party initiating connection, Bob in double-ratchet spec
generateRcvE2EParams :: (AlgorithmI a, DhAlgorithm a) => TVar ChaChaDRG -> Version -> PQEncryption -> IO (PrivateKey a, PrivateKey a, Maybe (PrivRKEMParams 'RKSProposed), E2ERatchetParams 'RKSProposed a)
generateRcvE2EParams g v = generateE2EParams g v . proposeKEM_
where
proposeKEM_ :: PQEncryption -> Maybe (UseKEM 'RKSProposed)
proposeKEM_ = \case
PQEncOn -> Just ProposeKEM
PQEncOff -> Nothing
-- used by party accepting connection, Alice in double-ratchet spec
generateSndE2EParams :: forall a. (AlgorithmI a, DhAlgorithm a) => TVar ChaChaDRG -> Version -> Maybe AUseKEM -> IO (PrivateKey a, PrivateKey a, Maybe APrivRKEMParams, AE2ERatchetParams a)
generateSndE2EParams g v = \case
Nothing -> do
(pk1, pk2, _, e2eParams) <- generateE2EParams g v Nothing
pure (pk1, pk2, Nothing, AE2ERatchetParams SRKSProposed e2eParams)
Just (AUseKEM s useKEM) -> do
(pk1, pk2, pKem, e2eParams) <- generateE2EParams g v (Just useKEM)
pure (pk1, pk2, APRKP s <$> pKem, AE2ERatchetParams s e2eParams)
data RatchetInitParams = RatchetInitParams
{ assocData :: Str,
ratchetKey :: RatchetKey,
sndHK :: HeaderKey,
rcvNextHK :: HeaderKey
rcvNextHK :: HeaderKey,
kemAccepted :: Maybe RatchetKEMAccepted
}
deriving (Eq, Show)
deriving (Show)
x3dhSnd :: DhAlgorithm a => PrivateKey a -> PrivateKey a -> E2ERatchetParams a -> RatchetInitParams
x3dhSnd spk1 spk2 (E2ERatchetParams _ rk1 rk2) =
x3dh (publicKey spk1, rk1) (dh' rk1 spk2) (dh' rk2 spk1) (dh' rk2 spk2)
-- this is used by the peer joining the connection
pqX3dhSnd :: DhAlgorithm a => PrivateKey a -> PrivateKey a -> Maybe APrivRKEMParams -> E2ERatchetParams 'RKSProposed a -> Either CryptoError (RatchetInitParams, Maybe KEMKeyPair)
-- 3. replied 2. received
pqX3dhSnd spk1 spk2 spKem_ (E2ERatchetParams v rk1 rk2 rKem_) = do
(ks_, kem_) <- sndPq
let initParams = pqX3dh (publicKey spk1, rk1) (dh' rk1 spk2) (dh' rk2 spk1) (dh' rk2 spk2) kem_
pure (initParams, ks_)
where
sndPq :: Either CryptoError (Maybe KEMKeyPair, Maybe RatchetKEMAccepted)
sndPq = case spKem_ of
Just (APRKP _ ps) | v >= pqRatchetVersion -> case (ps, rKem_) of
(PrivateRKParamsAccepted ct shared ks, Just (RKParamsProposed k)) -> Right (Just ks, Just $ RatchetKEMAccepted k shared ct)
(PrivateRKParamsProposed ks, _) -> Right (Just ks, Nothing) -- both parties can send "proposal" in case of ratchet renegotiation
_ -> Left CERatchetKEMState
_ -> Right (Nothing, Nothing)
x3dhRcv :: DhAlgorithm a => PrivateKey a -> PrivateKey a -> E2ERatchetParams a -> RatchetInitParams
x3dhRcv rpk1 rpk2 (E2ERatchetParams _ sk1 sk2) =
x3dh (sk1, publicKey rpk1) (dh' sk2 rpk1) (dh' sk1 rpk2) (dh' sk2 rpk2)
-- this is used by the peer that created new connection, after receiving the reply
pqX3dhRcv :: forall s a. (RatchetKEMStateI s, DhAlgorithm a) => PrivateKey a -> PrivateKey a -> Maybe (PrivRKEMParams 'RKSProposed) -> E2ERatchetParams s a -> ExceptT CryptoError IO (RatchetInitParams, Maybe KEMKeyPair)
-- 1. sent 4. received in reply
pqX3dhRcv rpk1 rpk2 rpKem_ (E2ERatchetParams v sk1 sk2 sKem_) = do
kem_ <- rcvPq
let initParams = pqX3dh (sk1, publicKey rpk1) (dh' sk2 rpk1) (dh' sk1 rpk2) (dh' sk2 rpk2) (snd <$> kem_)
pure (initParams, fst <$> kem_)
where
rcvPq :: ExceptT CryptoError IO (Maybe (KEMKeyPair, RatchetKEMAccepted))
rcvPq = case sKem_ of
Just (RKParamsAccepted ct k') | v >= pqRatchetVersion -> case rpKem_ of
Just (PrivateRKParamsProposed ks@(_, pk)) -> do
shared <- liftIO $ sntrup761Dec ct pk
pure $ Just (ks, RatchetKEMAccepted k' shared ct)
Nothing -> throwError CERatchetKEMState
_ -> pure Nothing -- both parties can send "proposal" in case of ratchet renegotiation
x3dh :: DhAlgorithm a => (PublicKey a, PublicKey a) -> DhSecret a -> DhSecret a -> DhSecret a -> RatchetInitParams
x3dh (sk1, rk1) dh1 dh2 dh3 =
RatchetInitParams {assocData, ratchetKey = RatchetKey sk, sndHK = Key hk, rcvNextHK = Key nhk}
pqX3dh :: DhAlgorithm a => (PublicKey a, PublicKey a) -> DhSecret a -> DhSecret a -> DhSecret a -> Maybe RatchetKEMAccepted -> RatchetInitParams
pqX3dh (sk1, rk1) dh1 dh2 dh3 kemAccepted =
RatchetInitParams {assocData, ratchetKey = RatchetKey sk, sndHK = Key hk, rcvNextHK = Key nhk, kemAccepted}
where
assocData = Str $ pubKeyBytes sk1 <> pubKeyBytes rk1
dhs = dhBytes' dh1 <> dhBytes' dh2 <> dhBytes' dh3
dhs = dhBytes' dh1 <> dhBytes' dh2 <> dhBytes' dh3 <> pq
pq = maybe "" (\RatchetKEMAccepted {rcPQRss = KEMSharedKey ss} -> BA.convert ss) kemAccepted
(hk, nhk, sk) =
let salt = B.replicate 64 '\0'
in hkdf3 salt dhs "SimpleXX3DH"
@@ -129,6 +389,11 @@ data Ratchet a = Ratchet
-- associated data - must be the same in both parties ratchets
rcAD :: Str,
rcDHRs :: PrivateKey a,
rcKEM :: Maybe RatchetKEM,
-- TODO PQ make them optional via JSON parser for PQEncryption
rcEnableKEM :: PQEncryption, -- will enable KEM on the next ratchet step
rcSndKEM :: PQEncryption, -- used KEM hybrid secret for sending ratchet
rcRcvKEM :: PQEncryption, -- used KEM hybrid secret for receiving ratchet
rcRK :: RatchetKey,
rcSnd :: Maybe (SndRatchet a),
rcRcv :: Maybe RcvRatchet,
@@ -138,20 +403,33 @@ data Ratchet a = Ratchet
rcNHKs :: HeaderKey,
rcNHKr :: HeaderKey
}
deriving (Eq, Show)
deriving (Show)
data SndRatchet a = SndRatchet
{ rcDHRr :: PublicKey a,
rcCKs :: RatchetKey,
rcHKs :: HeaderKey
}
deriving (Eq, Show)
deriving (Show)
data RcvRatchet = RcvRatchet
{ rcCKr :: RatchetKey,
rcHKr :: HeaderKey
}
deriving (Eq, Show)
deriving (Show)
data RatchetKEM = RatchetKEM
{ rcPQRs :: KEMKeyPair,
rcKEMs :: Maybe RatchetKEMAccepted
}
deriving (Show)
data RatchetKEMAccepted = RatchetKEMAccepted
{ rcPQRr :: KEMPublicKey, -- received key
rcPQRss :: KEMSharedKey, -- computed shared secret
rcPQRct :: KEMCiphertext -- sent encaps(rcPQRr, rcPQRss)
}
deriving (Show)
type SkippedMsgKeys = Map HeaderKey SkippedHdrMsgKeys
@@ -189,7 +467,7 @@ instance Encoding MessageKey where
-- | Input key material for double ratchet HKDF functions
newtype RatchetKey = RatchetKey ByteString
deriving (Eq, Show)
deriving (Show)
instance ToJSON RatchetKey where
toJSON (RatchetKey k) = strToJSON k
@@ -202,19 +480,32 @@ instance ToField MessageKey where toField = toField . smpEncode
instance FromField MessageKey where fromField = blobFieldDecoder smpDecode
-- | Sending ratchet initialization, equivalent to RatchetInitAliceHE in double ratchet spec
-- | Sending ratchet initialization
--
-- Please note that sPKey is not stored, and its public part together with random salt
-- is sent to the recipient.
-- @
-- RatchetInitAlicePQ2HE(state, SK, bob_dh_public_key, shared_hka, shared_nhkb, bob_pq_kem_encapsulation_key)
-- // below added for post-quantum KEM
-- state.PQRs = GENERATE_PQKEM()
-- state.PQRr = bob_pq_kem_encapsulation_key
-- state.PQRss = random // shared secret for KEM
-- state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret
-- // above added for KEM
-- @
initSndRatchet ::
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRange -> PublicKey a -> PrivateKey a -> RatchetInitParams -> Ratchet a
initSndRatchet rcVersion rcDHRr rcDHRs RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK} = do
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr))
let (rcRK, rcCKs, rcNHKs) = rootKdf ratchetKey rcDHRr rcDHRs
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRange -> PublicKey a -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> Ratchet a
initSndRatchet rcVersion rcDHRr rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) = do
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr) || state.PQRss)
let (rcRK, rcCKs, rcNHKs) = rootKdf ratchetKey rcDHRr rcDHRs (rcPQRss <$> kemAccepted)
in Ratchet
{ rcVersion,
rcAD = assocData,
rcDHRs,
rcKEM = (`RatchetKEM` kemAccepted) <$> rcPQRs_,
rcEnableKEM = PQEncryption $ isJust rcPQRs_,
rcSndKEM = PQEncryption $ isJust kemAccepted,
rcRcvKEM = PQEncOff,
rcRK,
rcSnd = Just SndRatchet {rcDHRr, rcCKs, rcHKs = sndHK},
rcRcv = Nothing,
@@ -225,17 +516,28 @@ initSndRatchet rcVersion rcDHRr rcDHRs RatchetInitParams {assocData, ratchetKey,
rcNHKr = rcvNextHK
}
-- | Receiving ratchet initialization, equivalent to RatchetInitBobHE in double ratchet spec
-- | Receiving ratchet initialization, equivalent to RatchetInitBobPQ2HE in double ratchet spec
--
-- def RatchetInitBobPQ2HE(state, SK, bob_dh_key_pair, shared_hka, shared_nhkb, bob_pq_kem_key_pair)
--
-- Please note that the public part of rcDHRs was sent to the sender
-- as part of the connection request and random salt was received from the sender.
initRcvRatchet ::
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRange -> PrivateKey a -> RatchetInitParams -> Ratchet a
initRcvRatchet rcVersion rcDHRs RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK} =
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRange -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> PQEncryption -> Ratchet a
initRcvRatchet rcVersion rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) rcEnableKEM =
Ratchet
{ rcVersion,
rcAD = assocData,
rcDHRs,
-- rcKEM:
-- state.PQRs = bob_pq_kem_key_pair
-- state.PQRr = None
-- state.PQRss = None
-- state.PQRct = None
rcKEM = (`RatchetKEM` kemAccepted) <$> rcPQRs_,
rcEnableKEM,
rcSndKEM = PQEncOff,
rcRcvKEM = PQEncOff,
rcRK = ratchetKey,
rcSnd = Nothing,
rcRcv = Nothing,
@@ -246,14 +548,17 @@ initRcvRatchet rcVersion rcDHRs RatchetInitParams {assocData, ratchetKey, sndHK,
rcNHKr = sndHK
}
-- encaps = state.PQRs.encaps, // added for KEM #2
-- ct = state.PQRct // added for KEM #1
data MsgHeader a = MsgHeader
{ -- | max supported ratchet version
msgMaxVersion :: Version,
msgDHRs :: PublicKey a,
msgKEM :: Maybe ARKEMParams,
msgPN :: Word32,
msgNs :: Word32
}
deriving (Eq, Show)
deriving (Show)
data AMsgHeader
= forall a.
@@ -262,8 +567,10 @@ data AMsgHeader
-- to allow extension without increasing the size, the actual header length is:
-- 69 = 2 (original size) + 2 + 1+56 (Curve448) + 4 + 4
-- TODO PQ this must be version-dependent
-- TODO this is the exact size, some reserve should be added
paddedHeaderLen :: Int
paddedHeaderLen = 88
paddedHeaderLen = 2284
-- only used in tests to validate correct padding
-- (2 bytes - version size, 1 byte - header size, not to have it fixed or version-dependent)
@@ -271,14 +578,16 @@ fullHeaderLen :: Int
fullHeaderLen = 2 + 1 + paddedHeaderLen + authTagSize + ivSize @AES256
instance AlgorithmI a => Encoding (MsgHeader a) where
smpEncode MsgHeader {msgMaxVersion, msgDHRs, msgPN, msgNs} =
smpEncode (msgMaxVersion, msgDHRs, msgPN, msgNs)
smpEncode MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
| msgMaxVersion >= pqRatchetVersion = smpEncode (msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs)
| otherwise = smpEncode (msgMaxVersion, msgDHRs, msgPN, msgNs)
smpP = do
msgMaxVersion <- smpP
msgDHRs <- smpP
msgKEM <- if msgMaxVersion >= pqRatchetVersion then smpP else pure Nothing
msgPN <- smpP
msgNs <- smpP
pure MsgHeader {msgMaxVersion, msgDHRs, msgPN, msgNs}
pure MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
data EncMessageHeader = EncMessageHeader
{ ehVersion :: Version,
@@ -288,10 +597,12 @@ data EncMessageHeader = EncMessageHeader
}
instance Encoding EncMessageHeader where
smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody} =
smpEncode (ehVersion, ehIV, ehAuthTag, ehBody)
smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
| ehVersion >= pqRatchetVersion = smpEncode (ehVersion, ehIV, ehAuthTag, Large ehBody)
| otherwise = smpEncode (ehVersion, ehIV, ehAuthTag, ehBody)
smpP = do
(ehVersion, ehIV, ehAuthTag, ehBody) <- smpP
(ehVersion, ehIV, ehAuthTag) <- smpP
ehBody <- if ehVersion >= pqRatchetVersion then unLarge <$> smpP else smpP
pure EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
data EncRatchetMessage = EncRatchetMessage
@@ -300,37 +611,123 @@ data EncRatchetMessage = EncRatchetMessage
emBody :: ByteString
}
instance Encoding EncRatchetMessage where
smpEncode EncRatchetMessage {emHeader, emBody, emAuthTag} =
smpEncode (emHeader, emAuthTag, Tail emBody)
smpP = do
(emHeader, emAuthTag, Tail emBody) <- smpP
pure EncRatchetMessage {emHeader, emBody, emAuthTag}
encodeEncRatchetMessage :: Version -> EncRatchetMessage -> ByteString
encodeEncRatchetMessage v EncRatchetMessage {emHeader, emBody, emAuthTag}
| v >= pqRatchetVersion = smpEncode (Large emHeader, emAuthTag, Tail emBody)
| otherwise = smpEncode (emHeader, emAuthTag, Tail emBody)
rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> ExceptT CryptoError IO (ByteString, Ratchet a)
rcEncrypt Ratchet {rcSnd = Nothing} _ _ = throwE CERatchetState
rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcNs, rcPN, rcAD = Str rcAD, rcVersion} paddedMsgLen msg = do
encRatchetMessageP :: Version -> Parser EncRatchetMessage
encRatchetMessageP v = do
emHeader <- if v >= pqRatchetVersion then unLarge <$> smpP else smpP
(emAuthTag, Tail emBody) <- smpP
pure EncRatchetMessage {emHeader, emBody, emAuthTag}
newtype PQEncryption = PQEncryption {enablePQ :: Bool}
deriving (Eq, Show)
pattern PQEncOn :: PQEncryption
pattern PQEncOn = PQEncryption True
pattern PQEncOff :: PQEncryption
pattern PQEncOff = PQEncryption False
{-# COMPLETE PQEncOn, PQEncOff #-}
instance ToJSON PQEncryption where
toEncoding (PQEncryption pq) = toEncoding pq
toJSON (PQEncryption pq) = toJSON pq
instance FromJSON PQEncryption where
parseJSON v = PQEncryption <$> parseJSON v
replyKEM_ :: PQEncryption -> Maybe (RKEMParams 'RKSProposed) -> Maybe AUseKEM
replyKEM_ pqEnc kem_ = case pqEnc of
PQEncOn -> Just $ case kem_ of
Just (RKParamsProposed k) -> AUseKEM SRKSAccepted $ AcceptKEM k
Nothing -> AUseKEM SRKSProposed ProposeKEM
PQEncOff -> Nothing
instance StrEncoding PQEncryption where
strEncode pqMode
| enablePQ pqMode = "pq=enable"
| otherwise = "pq=disable"
strP =
A.takeTill (== ' ') >>= \case
"pq=enable" -> pq True
"pq=disable" -> pq False
_ -> fail "bad PQEncryption"
where
pq = pure . PQEncryption
data InitialKeys = IKUsePQ | IKNoPQ PQEncryption
deriving (Eq, Show)
instance StrEncoding InitialKeys where
strEncode = \case
IKUsePQ -> "pq=invitation"
IKNoPQ pq -> strEncode pq
strP = IKNoPQ <$> strP <|> "pq=invitation" $> IKUsePQ
-- determines whether PQ key should be included in invitation link
initialPQEncryption :: InitialKeys -> PQEncryption
initialPQEncryption = \case
IKUsePQ -> PQEncOn
IKNoPQ _ -> PQEncOff -- default
-- determines whether PQ encryption should be used in connection
connPQEncryption :: InitialKeys -> PQEncryption
connPQEncryption = \case
IKUsePQ -> PQEncOn
IKNoPQ pq -> pq -- default for creating connection is IKNoPQ PQEncOn
-- determines whether PQ key should be included in invitation link sent to contact address
joinContactInitialKeys :: PQEncryption -> InitialKeys
joinContactInitialKeys = \case
PQEncOn -> IKUsePQ -- default
PQEncOff -> IKNoPQ PQEncOff
rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> Maybe PQEncryption -> ExceptT CryptoError IO (ByteString, Ratchet a)
rcEncrypt Ratchet {rcSnd = Nothing} _ _ _ = throwE CERatchetState
rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcVersion} paddedMsgLen msg pqMode_ = do
-- state.CKs, mk = KDF_CK(state.CKs)
let (ck', mk, iv, ehIV) = chainKdf rcCKs
-- enc_header = HENCRYPT(state.HKs, header)
(ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV paddedHeaderLen rcAD msgHeader
-- return enc_header, ENCRYPT(mk, plaintext, CONCAT(AD, enc_header))
let emHeader = smpEncode EncMessageHeader {ehVersion = minVersion rcVersion, ehBody, ehAuthTag, ehIV}
-- TODO PQ versioning in Ratchet should change somehow
let emHeader = smpEncode EncMessageHeader {ehVersion = maxVersion rcVersion, ehBody, ehAuthTag, ehIV}
(emAuthTag, emBody) <- encryptAEAD mk iv paddedMsgLen (rcAD <> emHeader) msg
let msg' = smpEncode EncRatchetMessage {emHeader, emBody, emAuthTag}
let msg' = encodeEncRatchetMessage (maxVersion rcVersion) EncRatchetMessage {emHeader, emBody, emAuthTag}
-- state.Ns += 1
rc' = rc {rcSnd = Just sr {rcCKs = ck'}, rcNs = rcNs + 1}
pure (msg', rc')
rc'' = case pqMode_ of
Nothing -> rc'
Just rcEnableKEM
| enablePQ rcEnableKEM -> rc' {rcEnableKEM}
| otherwise ->
let rcKEM' = (\rck -> rck {rcKEMs = Nothing}) <$> rcKEM
in rc' {rcEnableKEM, rcKEM = rcKEM'}
pure (msg', rc'')
where
-- header = HEADER(state.DHRs, state.PN, state.Ns)
-- header = HEADER_PQ2(
-- dh = state.DHRs.public,
-- kem = state.PQRs.public, // added for KEM #2
-- ct = state.PQRct, // added for KEM #1
-- pn = state.PN,
-- n = state.Ns
-- )
msgHeader =
smpEncode
MsgHeader
{ msgMaxVersion = maxVersion rcVersion,
msgDHRs = publicKey rcDHRs,
msgKEM = msgKEMParams <$> rcKEM,
msgPN = rcPN,
msgNs = rcNs
}
msgKEMParams RatchetKEM {rcPQRs = (k, _), rcKEMs} = case rcKEMs of
Nothing -> ARKP SRKSProposed $ RKParamsProposed k
Just RatchetKEMAccepted {rcPQRct} -> ARKP SRKSAccepted $ RKParamsAccepted rcPQRct k
data SkippedMessage a
= SMMessage (DecryptResult a)
@@ -338,7 +735,7 @@ data SkippedMessage a
| SMNone
data RatchetStep = AdvanceRatchet | SameRatchet
deriving (Eq)
deriving (Eq, Show)
type DecryptResult a = (Either CryptoError ByteString, Ratchet a, SkippedMsgDiff)
@@ -353,8 +750,9 @@ rcDecrypt ::
SkippedMsgKeys ->
ByteString ->
ExceptT CryptoError IO (DecryptResult a)
rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD} rcMKSkipped msg' = do
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError smpP msg'
rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
-- TODO PQ versioning should change
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError (encRatchetMessageP $ maxVersion rcVersion) msg'
encHdr <- parseE CryptoHeaderError smpP emHeader
-- plaintext = TrySkippedMessageKeysHE(state, enc_header, cipher-text, AD)
decryptSkipped encHdr encMsg >>= \case
@@ -368,7 +766,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD} rcMKSkipped msg' = do
SMMessage r -> pure r
where
decryptRcMessage :: RatchetStep -> MsgHeader a -> EncRatchetMessage -> ExceptT CryptoError IO (DecryptResult a)
decryptRcMessage rcStep MsgHeader {msgDHRs, msgPN, msgNs} encMsg = do
decryptRcMessage rcStep MsgHeader {msgDHRs, msgKEM, msgPN, msgNs} encMsg = do
-- if dh_ratchet:
(rc', smks1) <- ratchetStep rcStep
case skipMessageKeys msgNs rc' of
@@ -392,15 +790,23 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD} rcMKSkipped msg' = do
case skipMessageKeys msgPN rc of
Left e -> throwE e
Right (rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr}, hmks) -> do
-- DHRatchetHE(state, header)
-- DHRatchetPQ2HE(state, header)
(kemSS, kemSS', rcKEM') <- pqRatchetStep rc' msgKEM
-- state.DHRs = GENERATE_DH()
(_, rcDHRs') <- atomically $ generateKeyPair @a g
-- state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr))
let (rcRK', rcCKr', rcNHKr') = rootKdf rcRK msgDHRs rcDHRs
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr))
(rcRK'', rcCKs', rcNHKs') = rootKdf rcRK' msgDHRs rcDHRs'
-- state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || ss)
let (rcRK', rcCKr', rcNHKr') = rootKdf rcRK msgDHRs rcDHRs kemSS
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || state.PQRss)
(rcRK'', rcCKs', rcNHKs') = rootKdf rcRK' msgDHRs rcDHRs' kemSS'
sndKEM = isJust kemSS'
rcvKEM = isJust kemSS
rc'' =
rc'
{ rcDHRs = rcDHRs',
rcKEM = rcKEM',
rcEnableKEM = PQEncryption $ sndKEM || rcvKEM,
rcSndKEM = PQEncryption sndKEM,
rcRcvKEM = PQEncryption rcvKEM,
rcRK = rcRK'',
rcSnd = Just SndRatchet {rcDHRr = msgDHRs, rcCKs = rcCKs', rcHKs = rcNHKs},
rcRcv = Just RcvRatchet {rcCKr = rcCKr', rcHKr = rcNHKr},
@@ -411,6 +817,39 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD} rcMKSkipped msg' = do
rcNHKr = rcNHKr'
}
pure (rc'', hmks)
pqRatchetStep :: Ratchet a -> Maybe ARKEMParams -> ExceptT CryptoError IO (Maybe KEMSharedKey, Maybe KEMSharedKey, Maybe RatchetKEM)
pqRatchetStep Ratchet {rcKEM, rcEnableKEM = PQEncryption pqEnc} = \case
-- received message does not have KEM in header,
-- but the user enabled KEM when sending previous message
Nothing -> case rcKEM of
Nothing | pqEnc -> do
rcPQRs <- liftIO $ sntrup761Keypair g
pure (Nothing, Nothing, Just RatchetKEM {rcPQRs, rcKEMs = Nothing})
_ -> pure (Nothing, Nothing, Nothing)
-- received message has KEM in header.
Just (ARKP _ ps)
| pqEnc -> do
-- state.PQRr = header.kem
(ss, rcPQRr) <- sharedSecret
-- state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret KEM #1
(rcPQRct, rcPQRss) <- liftIO $ sntrup761Enc g rcPQRr
-- state.PQRs = GENERATE_PQKEM()
rcPQRs <- liftIO $ sntrup761Keypair g
let kem' = RatchetKEM {rcPQRs, rcKEMs = Just RatchetKEMAccepted {rcPQRr, rcPQRss, rcPQRct}}
pure (ss, Just rcPQRss, Just kem')
| otherwise -> do
-- state.PQRr = header.kem
(ss, _) <- sharedSecret
pure (ss, Nothing, Nothing)
where
sharedSecret = case ps of
RKParamsProposed k -> pure (Nothing, k)
RKParamsAccepted ct k -> case rcKEM of
Nothing -> throwE CERatchetKEMState
-- ss = PQKEM-DEC(state.PQRs.private, header.ct)
Just RatchetKEM {rcPQRs} -> do
ss <- liftIO $ sntrup761Dec ct (snd rcPQRs)
pure (Just ss, k)
skipMessageKeys :: Word32 -> Ratchet a -> Either CryptoError (Ratchet a, SkippedMsgKeys)
skipMessageKeys _ r@Ratchet {rcRcv = Nothing} = Right (r, M.empty)
skipMessageKeys untilN r@Ratchet {rcRcv = Just rr@RcvRatchet {rcCKr, rcHKr}, rcNr}
@@ -465,10 +904,13 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD} rcMKSkipped msg' = do
-- DECRYPT(mk, cipher-text, CONCAT(AD, enc_header))
tryE $ decryptAEAD mk iv (rcAD <> emHeader) emBody emAuthTag
rootKdf :: (AlgorithmI a, DhAlgorithm a) => RatchetKey -> PublicKey a -> PrivateKey a -> (RatchetKey, RatchetKey, Key)
rootKdf (RatchetKey rk) k pk =
let dhOut = dhBytes' $ dh' k pk
(rk', ck, nhk) = hkdf3 rk dhOut "SimpleXRootRatchet"
rootKdf :: (AlgorithmI a, DhAlgorithm a) => RatchetKey -> PublicKey a -> PrivateKey a -> Maybe KEMSharedKey -> (RatchetKey, RatchetKey, Key)
rootKdf (RatchetKey rk) k pk kemSecret_ =
let dhOut = dhBytes' (dh' k pk)
ss = case kemSecret_ of
Just (KEMSharedKey s) -> dhOut <> BA.convert s
Nothing -> dhOut
(rk', ck, nhk) = hkdf3 rk ss "SimpleXRootRatchet"
in (RatchetKey rk', RatchetKey ck, Key nhk)
chainKdf :: RatchetKey -> (RatchetKey, Key, IV, IV)
@@ -487,6 +929,10 @@ hkdf3 salt ikm info = (s1, s2, s3)
$(JQ.deriveJSON defaultJSON ''RcvRatchet)
$(JQ.deriveJSON defaultJSON ''RatchetKEMAccepted)
$(JQ.deriveJSON defaultJSON ''RatchetKEM)
instance AlgorithmI a => ToJSON (SndRatchet a) where
toEncoding = $(JQ.mkToEncoding defaultJSON ''SndRatchet)
toJSON = $(JQ.mkToJSON defaultJSON ''SndRatchet)
@@ -19,16 +19,20 @@ import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
newtype KEMPublicKey = KEMPublicKey ByteString
deriving (Show)
deriving (Eq, Show)
newtype KEMSecretKey = KEMSecretKey ScrubbedBytes
deriving (Show)
deriving (Eq, Show)
newtype KEMCiphertext = KEMCiphertext ByteString
deriving (Show)
deriving (Eq, Show)
newtype KEMSharedKey = KEMSharedKey ScrubbedBytes
deriving (Show)
deriving (Eq, Show)
unsafeRevealKEMSharedKey :: KEMSharedKey -> String
unsafeRevealKEMSharedKey (KEMSharedKey scrubbed) = show (BA.convert scrubbed :: ByteString)
{-# DEPRECATED unsafeRevealKEMSharedKey "unsafeRevealKEMSharedKey left in code" #-}
type KEMKeyPair = (KEMPublicKey, KEMSecretKey)
@@ -60,6 +64,18 @@ sntrup761Dec (KEMCiphertext c) (KEMSecretKey sk) =
KEMSharedKey
<$> BA.alloc c_SNTRUP761_SIZE (\kPtr -> c_sntrup761_dec kPtr cPtr skPtr)
instance Encoding KEMSecretKey where
smpEncode (KEMSecretKey c) = smpEncode . Large $ BA.convert c
smpP = KEMSecretKey . BA.convert . unLarge <$> smpP
instance StrEncoding KEMSecretKey where
strEncode (KEMSecretKey pk) = strEncode (BA.convert pk :: ByteString)
strP = KEMSecretKey . BA.convert <$> strP @ByteString
instance Encoding KEMPublicKey where
smpEncode (KEMPublicKey pk) = smpEncode . Large $ BA.convert pk
smpP = KEMPublicKey . BA.convert . unLarge <$> smpP
instance StrEncoding KEMPublicKey where
strEncode (KEMPublicKey pk) = strEncode (BA.convert pk :: ByteString)
strP = KEMPublicKey . BA.convert <$> strP @ByteString
@@ -68,6 +84,25 @@ instance Encoding KEMCiphertext where
smpEncode (KEMCiphertext c) = smpEncode . Large $ BA.convert c
smpP = KEMCiphertext . BA.convert . unLarge <$> smpP
instance Encoding KEMSharedKey where
smpEncode (KEMSharedKey c) = smpEncode (BA.convert c :: ByteString)
smpP = KEMSharedKey . BA.convert <$> smpP @ByteString
instance StrEncoding KEMCiphertext where
strEncode (KEMCiphertext pk) = strEncode (BA.convert pk :: ByteString)
strP = KEMCiphertext . BA.convert <$> strP @ByteString
instance StrEncoding KEMSharedKey where
strEncode (KEMSharedKey pk) = strEncode (BA.convert pk :: ByteString)
strP = KEMSharedKey . BA.convert <$> strP @ByteString
instance ToJSON KEMSecretKey where
toJSON = strToJSON
toEncoding = strToJEncoding
instance FromJSON KEMSecretKey where
parseJSON = strParseJSON "KEMSecretKey"
instance ToJSON KEMPublicKey where
toJSON = strToJSON
toEncoding = strToJEncoding
@@ -75,8 +110,22 @@ instance ToJSON KEMPublicKey where
instance FromJSON KEMPublicKey where
parseJSON = strParseJSON "KEMPublicKey"
instance ToJSON KEMCiphertext where
toJSON = strToJSON
toEncoding = strToJEncoding
instance FromJSON KEMCiphertext where
parseJSON = strParseJSON "KEMCiphertext"
instance ToField KEMSharedKey where
toField (KEMSharedKey k) = toField (BA.convert k :: ByteString)
instance FromField KEMSharedKey where
fromField f = KEMSharedKey . BA.convert @ByteString <$> fromField f
instance ToJSON KEMSharedKey where
toJSON = strToJSON
toEncoding = strToJEncoding
instance FromJSON KEMSharedKey where
parseJSON = strParseJSON "KEMSharedKey"