mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-10-16 06:00:06 +00:00
* xftp server: support storage time and BBS proofs of badge credential to extend it * update * types * implementation * update * remove permanent and FTTL * refactor * refactor * refactor * simplify * simplify * simpler * remove comments * add file expiration time to agent event * add test of upload with entitlement * update schema * fix header and test * fix store log encoding, add xftp store log tests * send entitlement proof in handshake * type, query * more fixes * test entitlement keys * update * fixes * fix * comment * remove log * simpler * ignore entitlement proofs in browser requests * remove condition --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
152 lines
6.4 KiB
Haskell
152 lines
6.4 KiB
Haskell
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DerivingVia #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
|
|
module Simplex.Messaging.Crypto.Entitlement
|
|
( Entitlement (..),
|
|
EntitlementCredential (..),
|
|
EntitlementProof (..),
|
|
EntitlementVerification (..),
|
|
MasterKey (..),
|
|
randomMasterKey,
|
|
entitlementBBSHeader,
|
|
entitlementIssuerKeys,
|
|
signEntitlement,
|
|
verifyCredential,
|
|
generateEntitlementProof,
|
|
verifyEntitlement,
|
|
)
|
|
where
|
|
|
|
import Control.Concurrent.STM
|
|
import Crypto.Random (ChaChaDRG)
|
|
import Data.Aeson (FromJSON (..), ToJSON (..))
|
|
import qualified Data.Aeson.TH as JQ
|
|
import Data.ByteString.Char8 (ByteString)
|
|
import qualified Data.ByteString.Char8 as B
|
|
import Data.Either (fromRight)
|
|
import Data.Map.Strict (Map)
|
|
import qualified Data.Map.Strict as M
|
|
import Data.Text (Text)
|
|
import Data.Text.Encoding (decodeUtf8', encodeUtf8)
|
|
import Data.Time.Clock (UTCTime)
|
|
import Data.Word (Word16)
|
|
import qualified Simplex.Messaging.Crypto as C
|
|
import Simplex.Messaging.Crypto.BBS
|
|
import Simplex.Messaging.Encoding
|
|
import Simplex.Messaging.Encoding.String
|
|
import Simplex.Messaging.Parsers (defaultJSON)
|
|
import Simplex.Messaging.Util ((<$$>))
|
|
|
|
newtype MasterKey = MasterKey ByteString
|
|
deriving newtype (Eq, Show)
|
|
deriving (StrEncoding) via (FixedBS "MasterKey" 32)
|
|
deriving (ToJSON, FromJSON) via (StrJSON "MasterKey" MasterKey)
|
|
|
|
data Entitlement = Entitlement
|
|
{ expiresAt :: UTCTime,
|
|
entitlementName :: Text,
|
|
extraInfo :: Text
|
|
}
|
|
deriving (Eq, Show)
|
|
|
|
data EntitlementCredential = EntitlementCredential
|
|
{ issuerKeyIdx :: Word16,
|
|
masterKey :: MasterKey,
|
|
entitlement :: Entitlement,
|
|
issuerSignature :: BBSSignature
|
|
}
|
|
deriving (Eq, Show)
|
|
|
|
data EntitlementProof = EntitlementProof
|
|
{ issuerKeyIdx :: Word16,
|
|
entitlement :: Entitlement,
|
|
entProof :: BBSProof
|
|
}
|
|
deriving (Eq, Show)
|
|
|
|
data EntitlementVerification = EVValid | EVInvalid | EVUnknownIssuer
|
|
deriving (Eq, Show)
|
|
|
|
instance Encoding Entitlement where
|
|
smpEncode Entitlement {expiresAt, entitlementName, extraInfo} =
|
|
smpEncode (strEncode expiresAt, entitlementName, Large $ encodeUtf8 extraInfo)
|
|
smpP = do
|
|
(expBs, entitlementName, Large extraBs) <- smpP
|
|
expiresAt <- either fail pure $ strDecode (expBs :: ByteString)
|
|
extraInfo <- either (fail . show) pure $ decodeUtf8' extraBs
|
|
pure Entitlement {expiresAt, entitlementName, extraInfo}
|
|
|
|
instance Encoding EntitlementProof where
|
|
smpEncode EntitlementProof {issuerKeyIdx, entProof, entitlement} =
|
|
smpEncode (issuerKeyIdx, entProof, entitlement)
|
|
smpP = do
|
|
(issuerKeyIdx, entProof, entitlement) <- smpP
|
|
pure EntitlementProof {issuerKeyIdx, entProof, entitlement}
|
|
|
|
entitlementBBSHeader :: BBSHeader
|
|
entitlementBBSHeader = BBSHeader "SimpleX badges v1"
|
|
|
|
entitlementMessageCount :: Int
|
|
entitlementMessageCount = 4
|
|
|
|
entitlementDisclosedIndexes :: [Int]
|
|
entitlementDisclosedIndexes = [1, 2, 3]
|
|
|
|
entitlementMessages :: MasterKey -> Entitlement -> [ByteString]
|
|
entitlementMessages (MasterKey mk) ent = mk : disclosedMessages ent
|
|
|
|
disclosedMessages :: Entitlement -> [ByteString]
|
|
disclosedMessages Entitlement {expiresAt, entitlementName, extraInfo} =
|
|
[strEncode expiresAt, encodeUtf8 entitlementName, encodeUtf8 extraInfo]
|
|
|
|
randomMasterKey :: TVar ChaChaDRG -> STM MasterKey
|
|
randomMasterKey g = MasterKey <$> C.randomBytes 32 g
|
|
|
|
signEntitlement :: BBSSecretKey -> Word16 -> MasterKey -> Entitlement -> IO (Either String EntitlementCredential)
|
|
signEntitlement sk keyIdx mk ent =
|
|
EntitlementCredential keyIdx mk ent <$$> bbsSign sk entitlementBBSHeader (entitlementMessages mk ent)
|
|
|
|
verifyCredential :: BBSPublicKey -> EntitlementCredential -> IO Bool
|
|
verifyCredential pk EntitlementCredential {masterKey, issuerSignature, entitlement} =
|
|
bbsVerify pk issuerSignature entitlementBBSHeader (entitlementMessages masterKey entitlement)
|
|
|
|
generateEntitlementProof :: Map Word16 BBSPublicKey -> EntitlementCredential -> BBSPresHeader -> IO (Either String EntitlementProof)
|
|
generateEntitlementProof keys EntitlementCredential {issuerKeyIdx, masterKey, issuerSignature, entitlement} ph =
|
|
case M.lookup issuerKeyIdx keys of
|
|
Nothing -> pure $ Left $ "no issuer key " <> show issuerKeyIdx
|
|
Just pk -> EntitlementProof issuerKeyIdx entitlement <$$> bbsProofGen pk issuerSignature entitlementBBSHeader ph entitlementDisclosedIndexes (entitlementMessages masterKey entitlement)
|
|
|
|
verifyEntitlement :: Map Word16 BBSPublicKey -> BBSPresHeader -> EntitlementProof -> IO EntitlementVerification
|
|
verifyEntitlement keys ph EntitlementProof {issuerKeyIdx, entProof, entitlement} =
|
|
case M.lookup issuerKeyIdx keys of
|
|
Nothing -> pure EVUnknownIssuer
|
|
Just pk ->
|
|
(\valid -> if valid then EVValid else EVInvalid)
|
|
<$> bbsProofVerify pk entProof entitlementBBSHeader ph entitlementDisclosedIndexes entitlementMessageCount (disclosedMessages entitlement)
|
|
|
|
entitlementIssuerKeys :: Map Word16 BBSPublicKey
|
|
entitlementIssuerKeys =
|
|
M.fromList
|
|
[ (1, key "mW_5Zp1wHnXDF56wOZwFcRjGrf0GLLsfyymIQDqYoWfjfvS7oQWSfi7hH65N8JhuE9x8wbKXHidnQLO4GnOSMP_bRKUMH1qIzv5SQKFHNM8G4PaWcTcri8iZLc-3xhSI"),
|
|
(2, key "odGCB7uVDXTURsHgSvSciByV4Q3-3ZvEB8myDsDJqm-PwOYc5-At36uc7n_pyUDxEQEHr9i4RJgFih2FSArPW-EQBXNPNf4wTtA0znn74qLEGc4fh9pVYPEIm_ZGbnsJ"),
|
|
(3, key "txkT2003WMjc43KvYvPKEcR970NLmw5UZY51eUqgk91sgp53idt1HTlKYvnrEttJDFMlctYf1-bpri0e9DhBQ-xk1J4WoLN2uif_1OcA1pGCobpk9lwtsq1Idek4biy0"),
|
|
(4, key "q_YzegihaLYrEm9z3cAghsfDGNZfXuEpQGMJERJQS4M0Szl4gvSC_fV_muKc3NIMA_8iYuBN8qyvb5U55RctCRn3kleFQ4sqf-WBgoydX6UVo7BsYcUbXWWEFZXlOGIH"),
|
|
(5, key "oqymHASH_okefShrnz4HnTooUNlE1WoDRnSrgd0bTCpOacgJWBsMpwZpdmYlX-vQAKAC_zmI4VdKoOznnhW-sdUXZw6bthCi5JYjGxCR1Co27i1tix5UXCTbR5Jp901-"),
|
|
(6, key "kDqaB6zKSRp_97QPFj5JPDlo0vzfSTLSp9goFx1qajv4q4H6dR6BbkmWZ4xx_9Q2AxmcpqcV0ethz1OH-Jk_Sz2J1mIz1PUVM9LkdLhi_PNtqhezzO5dbVs-HJ1fNqe6"),
|
|
(7, key "rl36D5mg2N3NmmEybxE_RBeU9YZ_zeXNPfp7ZMLtUEuf2Mo4OQM_Up1v5rX_IqICD-AIJcuyptEBsELx_PJQzpmiNuG5I4cWO6HkRKtc6fVFvgZMrDJjaascPd1CIyxX"),
|
|
(8, key "joM3Bnt7JPt5JiwQwERHGjro2iVZ0mPD_clUh4hzkhxvbjuFrWuTmfSNA8PWBqGKEGNl13aRi1pMf6yY14E27c5C71JxWm7T-rZaBrGPEUWifhD-qidWuf3PU7KJCCWd")
|
|
]
|
|
where
|
|
key = fromRight (error "bad base64 in BBSPublicKey") . strDecode . B.pack
|
|
|
|
$(JQ.deriveJSON defaultJSON ''Entitlement)
|
|
|
|
$(JQ.deriveJSON defaultJSON ''EntitlementCredential)
|
|
|
|
$(JQ.deriveJSON defaultJSON ''EntitlementProof)
|